- web6047 - (2021/09/10(金) 現在、システム調整中のため、一部の表示がおかしいかもしれません)

[横幅 1024px以下]

web6047 2021年 11

プログラミングやRPG(作るほう)が好きな人の日記





パソコン使用時間管理の公開:

この表は、このウェブページの管理人のパソコンの使用時間を管理・制限するためのものです。





































NO PC WEEK に代わる PC 使用制限のしくみ(新β版)
No. A1.
開始時
運動
A2.
勉強
1問
A3.
終了時
運動
H1. 予定
作業内容
H2. 予定
作業詳細(進捗%)
判定×の理由
G2.
完成を
急いで
いる?
B. 実際
開始時刻
C. 予定
使用時間
(当日限度)
D. 予定
終了時刻
E. 実際
終了時刻
F. 実際
使用時間
G1. 判定
◎ 9分以下
○ 10分~19分
△ 20分~29分
× 30分以上
452 ページ上のプログラムリスト表示機能の復帰(再度作成) プログラムの整頓 no 22:20 1:30(1:30) 23:50 24:04 1:44
451 × 「98ハードに強くなる本II」
サンプルプログラム実行
BASICのCLEAR文
について
メモリ表示ツール作成
これ(画像)
yes 14:15 1:30(unlock) 15:45 30:00 15:45 ×
450 × Web ページ記事作成 PC-9801 UV11 について no 14:13 1:30(unlock) 15:43 25:30 11:17 ×
449 「98ハードに強くなる本II」
サンプルプログラム実行
BASICのCLEAR文
について
理解に時間がかかった
yes 19:00 1:30(unlock) 20:30 22:03 3:03 ×
448 「98ハードに強くなる本II」
サンプルプログラム実行
準備
サンプルの頭数把握等
yes 14:00 1:30(unlock) 15:30 18:13 4:13 ×
447 × ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる yes 21:30 1:30(1:30) 23:00 23:19 1:49
446 ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる no 21:30 1:30(1:30) 23:00 23:21 1:51
445 × グリフォンモデリング 翼を青光りさせる
キリの良いところまでやろうとしてしまった
no 22:52 1:00(2:00) 23:52 24:29 1:37 ×
444 ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる no 15:35 1:00(2:00) 16:35 16:45 1:10
443 × × × ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる
デバッグ。Perl自体のバグのように思えたが、原因の究明はできなかった。
yes 21:30 1:00(4:00) 22:30 26:51 5:21 ×
442 ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる no 19:50 1:30(4:00) 21:20 21:28 1:38
441 ページ上のプログラムリスト表示機能の復帰(再度作成) クラスやメソッドを認識させる no 16:45 1:30(4:00) 18:15 18:28 1:43
440 ページ上のプログラムリスト表示機能の復帰(再度作成) つづき。
もうすぐ終わる…その16
yes 22:20 1:30(1:30) 23:50 23:49 1:29
439 ページ上のプログラムリスト表示機能の復帰(再度作成) つづき。
もうすぐ終わる…その15
完成を急いでいた
no 14:10 1:00(2:00) 15:10 18:45 4:35 ×
438 ページ上のプログラムリスト表示機能の復帰(再度作成) つづき。
もうすぐ終わる…その14
完成を急いでいた
yes 13:55 1:30(4:00) 15:25 23:13 7:48 ×
437 ページ上のプログラムリスト表示機能の復帰(再度作成) つづき。
もうすぐ終わる…その13
yes 19:35 1:30(1:30) 21:05 21:14 1:39
記録しなかった長時間作業あり
作業内容:10月の絵についての記事
時間:一日中
記録しなかった長時間作業あり
作業内容:アセンブラについての記事
時間:数時間

この表の意図:

多くの人はパソコンのやりすぎやネットゲームのやりすぎには困っていると思います。

参考に言うと、この表を使う前の私は 1 回の PC 使用時間がノンストップで 17 時間というときもあったし、平均で言うと毎日 9 時間はやっていたと思います。

この表を使ってパソコンの使用時間を 事前に決めてネット上に公開 することで、パソコンのやりすぎを防止できたら、と思います。

また、数年前から考えてきましたが、そういう徹夜とか長時間作業をするよりも、昼間の短時間作業のほうが生産性は高いのではないかと思います。そういう意味でも期待しています。

※以前は NO PC WEEK と称してパソコンを使用しない期間を設けることでやりすぎに対処してきましたが、もっと具合の良い方法はないかと考え、この表を使うようになりました。

記入の規則:

  • 日付は表示していません(私の生活パターンをすべて知らせるのはよくないから)。しかし、白と灰色の色分けは、同じ色の連続で同じ日を表しています。
  • 左端の「A1. 運動」、「A2. 1問」、「A3. 運動」について
    この表の目的とは異なりますが、ついでとして、遊び100%の毎日を送るときでも勉強の習慣を忘れないために、たった1問で良いので解くことにします。
    正直言うと毎回遊ぶ前に必ず1問勉強するのは心が折れそうです。でも慣れさえすれば…と思います。
    行ったら◎、行わなかったら× を記入します。
    2020年11月20日ぐらいから「A1. 運動」を追加しました。パソコンを行う前に運動することを強制するものです。腕立て伏せ10回とか腹筋10回とかです。
    (ホントだったら30回くらいはやりたいところですが、私は体の調子が悪いので、10回程度にしています)
    2021年6月26日「A3. 運動」を追加。PC使用の終了時にも運動する。
    A1はPC漬けによる筋力衰え対策の意味で、筋トレを行い、
    A3はPC作業でこった体をほぐす意味で、ラジオ体操やストレッチ体操を行う。
  • 右端の「G. 判定」について
    「D. 予定終了時刻」と「E. 実際終了時刻」を比べて、オーバーした時間によって判定を行います。
    ◎ 9分以下
    ○ 10分~19分
    △ 20分~29分
    × 30分以上 (オーバー理由を記載する。理由の統計を取れば何が問題なのか把握でき、改善しやすいです)
  • 平日の 1.5h、2.0h の PC 使用の連続が負担になっているので、週のスケジュールは下記のようにする。
    月水で PC 使用しないことでうまいぐあいに「体力回復」されて、「生活」がうまく回り、プログラミング以外の「創作活動」(イラスト等)に時間が取れることを期待します。
    月:0h
    火:1.5h
    水:0h
    木:1.5h
    金:1.5h
    土:4hまで(休日で、翌日も休日)
    日:2hまで(休日で、翌日平日) ←早起きなおかつ(日々の買い物とかじゃなく)散歩するなら 3h やって良い
    (※2021年2月2日:週の各時間を調整しました)
  • ×、△、"記録しなかった長時間作業" が多いと思ったら、バランスをとるために、NO PC WEEK※ を1週間実施する。
    NO PC WEEK とは私自身の健康のために私のパソコンの使用を制限する期間です)


ちなみに、分単位で記録を取ったりして、だいぶマメに見えるかもしれませんが、Windows の日本語入力(MS-IME)で「いま」と入力し、 変換 キーを押さずに ボタンを押すと現在の時刻になります。道具の便利さが人をマメに見せるのかもしれません。

例外事項:

  • 「E. 実際終了時刻」のあと、プログラミングの場合のみスッパリ終了しないで、今後のプログラミングの方針をテキストファイルに書くのは OK にしています。


「スーパーPC WEEK」:

連休中(3連休以上)に、NO PC WEEK をオフにして好きなだけパソコンを使ってよいとする期間を、「NO PC WEEK」に対して「スーパーPC WEEK」と言う。

ただし以下の決まりを守ること。

  • その日に自由にパソコンを使ってもよいが、1回あたり、いつものように表に記入すること。(開始時運動、勉強1問、終了時運動も行うこと)
     (理由: 表が結構有効だから)
  • 1回ごとに、掃除、炊事、洗濯や、別の趣味など、まとまった作業を はさむこと。
     (理由: 1回1回がが連続してつながると回を分けている意味(身体に無理をかけない)がなくなるから)
  • 24時に就寝するよう努めること。(強制ではないが、つとめること)
     (理由: 深夜の神秘的な時間は魅力だが、安定したリズムも好ましいから)
  • 連休の最終日は通常通りとする。
     (理由: 翌日は仕事なので影響するから)
  • 1回の使用時間は2時間を最大とすること。

なお、表の中央やや右寄りの「C. 予定 使用時間(当日限度)」列の "当日限度" には UNLOCK と記入する。


中途結果:

結構いい結果になっています。炊事や掃除、散歩、早起きなどが好ましいリズムでできるようになりました。

(2020年9月6日追記:散歩、早起きは最近あまりできていません。掃除や炊事は理想的にできています)

また、パソコン以外の趣味も進むようになりました。電子回路、ガンプラ、RPG のプログラム以外のモンスターイラストやストーリーなど RPG の肉付け部分の創作、勉強、昔好きだったペーパークラフト等々。

そしてパソコンの趣味自体も深夜遅くまで行うよりも質が高くなったように感じます。制限された短い時間の中で結果を得ようとするので、取捨選択が行われているし、時間が終了して、空いた時間ができ、それがほどよい休憩になり、今後の作業の方針を落ち着いて検討することもできます。それが質につながっているのかなと思います。

この取り組みが、15 才 ~ 18 才くらいまでの高専(中退)に所属していた時に実施できていたら良かっただろうなと思います。でもそれくらいの年齢では経験が浅く、このような効果的なルール作りを行うことはできなかったと思います。自分は人に比べて「創作意欲」や「ゲームで遊ぶ欲望」におぼれやすいところがあり、そのコントロールはとても難しいです。

2021年10月23日追記:

ここで決めた規則通りにできていないこともありますが、それでも確実なメリットがあるのでここに箇条書きで記します。

  • 事前事後に運動することで、身体の「こり」がある程度解消されるのを期待できる。
  • 規則正しい生活になっている。食事、掃除、ふろ等、以前は後回しにしていたものが、時間通りにできている。
  • 「平日月水はパソコン作業を行わない」という規則が、よいリズムになっている。平日毎日パソコンに向かっていたら、疲れもたまるだろうと思う。
  • 表中にどんな作業をしているのか記入しているので、訪問者に日ごろのパソコン作業の内容を、わりとリアルタイムに知らせることが出来る。

他にもメリットはありますがたくさん書いてもしょうがないのでここまで。

また、反面、デメリットもあるので少し書いておきます。

  • 普通の人はやらないことを行い、なおかつWeb ページ上で公開しているので、「おかしな人だ」、「病気だろ」と思われてしまう。
  • 「創作は、生活のリズムを規則正しくしたほうが生産性が上がる」と考えていたが、ガツっと進めたいときに時間制限がかかり、やきもきするし、実際、やりたいことが遅々として進まない。

デメリットはそれくらいしか思いつきません。我ながらメリットの多い表なんだと再確認しました。


自分の家族にすすめたい方へ:


(これは イラストAC の無料素材です)

パソコンのやりすぎやネットゲームのやりすぎは社会問題にもなっているので、「うちの子についてなんとかしないと…」と思っているご家族の方は多くいらっしゃると思います。

私の両親も過去に私について問題にしていました。学校へ行かず、毎日朝までパソコンに向かい、悶々としていたんです。


この表はその家族が困っていたときから 30 年後に、私が自分で必要を感じて作ったものです。

私は今 一人暮らしをしていて、自分で生計を立てる中、パソコンにおぼれた生活をすると、生活がうまく回らなくなるんです。

具体的には、

  • 人前で疲れた顔を見せてしまい、人間関係がうまくいかなくなる。もっと具体的には、職場、とこや、お店のレジ、歯医者。
  • 掃除、洗濯、炊事が後回しになり、実質、それらを行う時間がなくなってしまう。深夜遅くや翌日に回したり、行わなかったりする。

これらを改善するために表を作りました。


でも、このような必要にせまられて「自分の動機で始めた場合」と、「人からすすめられて始めた場合」とでは、結果が異なると思います。

自分の切実な動機で始めたなら自分から進んでこの表を活用すると思いますが、外から押し付けられたものはなかなか定着しないものです。

あまり適当なことは言えませんが、「中途結果」タブの中の青い部分で書いたことは、本人にとって得になることなので、「ときどき休憩して、他のあの趣味やってみたらどうだ?」とか「ときどき休憩したほうがプログラミングの質が上がるって話だぞ?」という形ですすめてみたらどうでしょうか。(それでも最終的には自立してもらうことは必要だと思いますが)


私が両親を困らせていたときに、突然、外へ一人で出て行って、一人暮らしを始めたり、接客業を始めたり、いくつか資格取得したりといろいろ行えた理由というのは、正直言ってわかりません。(※しかし途中で失業して2度、実家に戻ったことがあります。1 回目は 21 才くらいのときに 5 年間、2 回目は 35 才くらいのときに1年未満、実家にいて、何もしてなかったり働いたりしていました)

私が両親を困らせていたのは 16 才 ~ 20 才くらいの学生のころですが、そのころ家族と私自身と友人たちがみんなそれぞれ、私の生活について心配したり困ったり悩んだり、あの手この手を試したりしていました。そういう煮詰まったような状況が運命をそのように(解決の方向へ)動かすのかもしれません。運命がどうの というのは変ですが、そのくらいのことしか言えません。何かしら取り組む必要があるということですかね。


この社会問題はクリアーすべきものみたいです。



最近観ているアニメ:

日付
(上ほど新しい)
タイトル無料配信
(各話配信日)
公評価私評価
2021/7/15~視聴中 ドラゴンボール(リンクは Yahoo GYAO で検索)
冒険・格闘技/1984年週刊少年ジャンプ/各話25分程度
月・水・金・・ ★★★★
4.8
★★★★
4.5
2021/5/18~視聴中 はじめの一歩(リンクは Yahoo GYAO で検索)
ボクシング/1989年週刊少年マガジン/各話25分程度
大人になってわかる、ドラゴンボールを上回る面白さ。スケベ表現がたまにあるので男性向け?
10月16日(土) 追記
シリーズが「New Challenger」という新しいものに変わりました。
1話2話を見る限り、あまり面白くなくなってしまったかも。
でもその後、数話見ていると、以前ほどではないけど結構面白いです。
月・水・・土・ ★★★★
4.5
★★★★★
5

最近観た映画:

日付
(上ほど新しい)
タイトル 無料配信
(日付まで)
公評価 私評価
2021/10/25(月) バーニング・オーシャン【吹き替え版】(リンクは Yahoo! GYAO! で検索)
ディザスター(実話)/2016年アメリカ/1:47:14/マーク・ウォールバーグ(トランスフォーマー)、カート・ラッセル(バックドラフト)
2010年メキシコ湾原油流出事故」(リンクは Wikipedia)をもとに製作されたディザスタームービー。
技術的不手際により掘削中の海底油田から逆流してきた天然ガスが引火爆発し、126人の作業員のうち11人が行方不明となり、17人が負傷した。
下記のアンダーウォーターと同じく、描写は完ぺき。なおかつ、面白かった。
2021/11/5
までGYAOにて無料配信
★★★★☆
4
★★★☆☆
3.8
2021/10/23(土) アンダーウォーター (字幕版)(リンクは Amazon で検索)
SF/2020年アメリカ/1:35:00
なんか、観ていてつまらなかったのは私だけかな?
最初にウイルスを におわせておいて、におわせただけなのもおかしいなと思う。
描写は完ぺきで非の打ちどころはなかったと思います。
2021/10/31
まで100円
Amazon月替わり100円
★★★☆☆
3.8
★★★☆☆
3

最近買ったもの:

日付
(上ほど新しい)
タイトル 公評価 私評価
2021/8/7 曲:アンインストール(リンクは Apple Music の当該ページへ)
2007年の「ぼくらの」というロボットアニメの主題歌
歌詞を読みましたが、「突然現れた新しい現実に対し、受け入れ、やるっきゃない」と言っているのかなぁ…
★★★★★
5
★★★★
4
2021/7/9 ゲーム:ザ・トリロジーズ -T&E SOFT / XTAL SOFT COLLECTION-(リンクは EGG の当該ページへ)
PCレトロゲームのセット。RPG開発の研究のために予約で購入。発売日は未定だそうです。
発売前 発売前
2021/5/29 曲:最強○×計画(リンクは Apple Music の当該ページへ)
2006年の「すもももももも 地上最強のヨメ」というアニメの主題歌
★★★★
4.5
★★★★
4


自己紹介:

47才、男、B型(BB)

電子機器の基板を製造する工場で、派遣で働いています。

プログラミングが好きで小学校5年生のころからずっと続けています。

アニメ見ます、ガンプラ作ります、映画見ます、ゲーム音楽いっぱい聴きます。

ページ上にていろいろ才能(少々 粗削りな才能)を発揮していると思いますが、なるべく自分だけで終わらないようにといつも思っています。

いろいろ厳しい考え方も持っていますが、厳しすぎないように全体とのバランスも考えています。

将来の夢は5つくらい持っていますが、生きてる間に実現できそうにありません。

※なお、現在このホームページには私への連絡手段がありません。気が向いたら用意します。


日記: (このサイトのメインコンテンツです。上の記事ほど新しい記事です)

2021/11/26(金)

NO PC WEEK 開始

恥ずかしながら、NO PC WEEK を開始します。

最近パソコンのやりすぎでいけないので。。

難しいけど、ちゃんとした対策が取れるまで無期限です。

大丈夫かな。。。でも必要です。


追記: 2021年11月30日

ちゃんとした対策を立てました。

ちょっとバカっぽいですが、パソコンのコンセントに「電源タイマー」(リンクは Amazon 商品ページへ)を設置して、毎日、

昼12:00 電源オン

夜23:00 電源オフ

となるようにしてみます。

これで、午前中は電源を入れられず、夜は23時にパソコンへの電気の供給が止まります。

メインのパソコンはノートパソコンなので、電気が来なくてもバッテリーで駆動し続けることができますが、パソコンの横に置いてあるディスプレイの電源は切れるので 便利なマルチモニタ環境は停止するし、パソコンはバッテリ駆動で省エネルギーモードになる(そう設定してある)ので、通常の作業はできなくなります。

せまく暗い画面でバッテリを気にしながらの作業になるので、作業を終わらせる気になるのではないかと思います。

しかし、そうやって、自分のパソコンへの気持ちを強制してしまうと不満がたまると思うので、ちょっと工夫して自分自身にこの制限のメリットを毎回知らせる(すりこむ)ようにします。メリットとは

健康的にやらないと、生活は不安定になるし、どんなに頑張って時間をかけて作業しても作品の完成はあっても花は咲かない(幸せになれない)と思うんです。

これで安定するといいんだけどな。。


追記: 2021年12月11日

通販で頼んでいた「電源タイマー」届きました。

それで、昼間12時に電源供給開始、夜間23時に電源供給終了、という環境にできました。

昨晩はちゃんと23時に電源供給が止まりました。

ただし、誤差があり 23:12 にオフ、12:12 にオンとなっています。

このタイマーは 15 分刻みでの設定なので修正すれば 22:57 にオフ、11:57 にオンにできます。

またはタイマー本体の電源を切った状態(本体のコンセントを抜いた状態)でタイマー本体の現在時刻を設定してから、その時刻にタイマー本体の電源を入れれば(本体のコンセントを差し込めば)正確に合わせられると思います。


昨日は「今日は 23 時までパソコンできるぞ」と期待して 18 時に仕事から帰ってきました。

しかし、疲れていたので休んだり、、クラウドファンディングの応援メッセージを書くのに 1 時間かけたり、、夕飯のシチューを作ったり、、いろいろやっているうちに、なんと落ち着いた時間が 22:50 でした。

「これでは 10 分くらいしかパソコンできないではないか!」

と自分自身に文句を言いましたが、せっかく用意したオンオフ環境なので、しぶしぶ自分で決めた 23 時終了に従い、パソコンはやらずに 23:50 くらいに就寝しました。

翌朝は土曜日にしては早めの 8:30 に目が覚めました。

そこで思ったんですが、もし、昨晩、「10 分くらいしかできない!」と思った時に「今日はパソコンをやる時間を楽しみにしていたのだから、2 時間くらいは…」と考えてパソコンを始めていたら、ずるずると夜遅くまでパソコンに ひたっていたのでは。。そして翌朝は昼 11 時ごろに遅く起床し、ちょっと疲れている、という状態になっていたのではないか…。

普通の人は夜遅くになったら、素直に「今日は期待していたけどもう遅いからしかたないか…」と自分を律することができるんだと思います。

パソコンやゲームにおぼれている人は、要所で自分を律することができません。(生活のリズムと自分の好きなことの優先順位が逆転している)

タイマーを設置したことで、その普通の人の行動を疑似体験することができました。

たとえ期待していた自分の好きなものでも、時間が来ているならあきらめる、ということが結局のところ必要だ、ということですね。(大人の判断)


ただし、このタイマー設置も完ぺきではなく、いくつか弱いところがあります。

ですが、実際は、


…まぁ、しばらくはタイマーの効果の様子を見ていこうと思います。


2021/11/21(日)

PC-9801 UV11 購入に踏み切った - その8

/* PC-9801 UV11 購入に踏み切った 各記事 一覧 */
その1(2021/9/18 土)
買いました。(品物未着)
購入品の内訳を紹介。

その2(2021/9/18 土)
届きました。
状態の確認。

その3(2021/9/19 日)
私の記念品としての確認。
その4(2021/9/24 金)
BIOS 取得作業
実機動作
→ 問題3つ発生。
その5(2021/9/25 土)
問題3
"ディスク書込禁止" 解決
そして、BIOS 取得

その6(2021/10/16 土)
ソフトウェア2点、ファイラー「FL」とテキストエディタ「JED」導入
その7(2021/11/2 火)
98実機でのアセンブラ
「98ハードに強くなる本II」のサンプルプログラム
その8(2021/11/21 日)
3モード FDD 壊れて再購入
N88-BASIC を購入
新品フロッピー10枚廃棄



PC-9801 などレトロ PC に興味がある人向けのお話になっております。あるあるばなしです。

-- 今回のもくじ --

  1.  3 モード FDD 買い直し
  2. BASIC 競り落とし
  3. マウス競り落とし
  4. フロッピーディスクの不幸
  5.  9 月から始めたレトロ PC 関連の全出費


 3 モード FDD 買い直し

▼左が NG の FDD、右が2台目購入のFDD(どちらも中古品)

 (この写真…あるある!)

写真左の FDD は購入した当初から挙動がちょっとおかしかったです。(あるある!)

読み込みは一応できますがときどき読めないことがありました。

読めないときは「イジェクトボタン」を軽く押すと読み始める、という不思議な症状でした。

書き込みも一応できますが、フォーマットや「ディスクイメージの復元」などディスク全体に関わる書き込みはほとんどできませんでした。


fig.
▲3モード FDD を解体しているところ

解体してヘッドの洗浄を 2 度 3 度行いましたがそれでも直らないので、コンデンサが劣化しているのかと考え、それまで手を付けなかった深いところまで解体したところ、内部のヘッドをずらしてしまったのかそれとも静電気の影響なのか、症状がさらに悪くなり、とうとう使えなくなってしまいました。

それまで一応は読み書き出来ていたのだから そのままで良かったのに。。と深いところまで解体したことを後悔しました。(あるある!)

なお、この3モード FDD の型式は、

TOSHIBA製 PA2680U

で、解体した内部の機械の型式は

Y-E DATA製 YD-8U10

いろいろなメーカーの FDD はみんなコレなんだそうです。

左の画像リンクをクリックすると 画像を拡大 します。



Windows で使える3モード FDD がダメになると、PC-9801 は Windows とやりとりできない孤立状態になります。RS-232C というシリアル通信でデータ通信するという手段もありそうですが、今からそのためのソフトウェアを C 言語で作るのは大変で時間もかかるので、しかたなく通販で再び同じ FDD を購入することにしました。(あるある!)

(Amazon にて 3,380 円 - 手持ちギフト券 2,900円 で、472 円の出費でした)

私が購入したことで当該商品の在庫は現在1個になったようです。(Amazon その商品ページ

3モード FDD の別の機種を選択することもできたんですが、一度は読み書きできて良い思いをさせてくれた、という実績があるので、同じ機種を選びました。

それで届いた FDD はあっさりと「正常動作」でした。(あるある!)

洗浄したり解体したりと格闘したのは、もともと状態の悪い品物だったから、ということでした。

(あるあるある!) ※あるある発言はもうこの辺にしておきます。


BASIC 競り落とし

「98ハードに強くなる本II」は、サンプルプログラムが「アセンブリ言語」または「N88-日本語BASIC(86)言語」で掲載されています。

PC-9801 UV11 本体はこの本がきっかけで購入したようなものなので、そのサンプルプログラムを動かすための BASIC はあったほうが良いなと思い、オークションを毎日チェックしていました。


「N88-日本語BASIC(86)」オークション1回目

このリンク先の写真を見ると、だいぶ小汚いケースですが、中は綺麗な様子だったので入札しました。

1,500 円スタートで競売が開始され、終了当日まで私しか入札者がいなかったので、「1,500 円で落とせるだろう」と思って競売の様子は見ないで落札のお知らせメールを待っていましたが、終了時刻が過ぎてメールを確認すると、なんとメールは来ていませんでした。

あれ??もしや??と思って商品ページを見たら私ではなく別の人が落札していました。

残念!!

その人は終了間際の30分前くらいに入札して、私の入札 上限額の 3,000 円よりも 100 円高い 3,100 円で落札していました。

なんか、終了間際に入札するのが、落札のポイントなのだろうか……?

かすめ取るように落札するなんてひどいなぁ。。と被害妄想しました。


「N88-日本語BASIC(86)」オークション2回目

くやしさの勢いに乗ったのかどうかわかりませんが、そのまま、代わりに別の出品を落札しました。

即決価格 5,000 円。当初の計画は「3,000 円 ~ 9,000 円」なので計画 ど真ん中ではありますが、ちょっと最近出費がかさんでいるので、もっと安いほうが、、と思いましたが、まぁいいかと。

商品ページの写真を見るとシリアル番号が紙で隠されていますが、これは聞くところによると悪いことではないらしいです。

シリアル番号はユーザーサポートを受けるときの証明みたいなもので、他人の目に触れてはいけないから隠しているのだそうです。

(でも Vz エディタを落札したときは、あれはたしかに違法コピーでした)


落札できなかった上記1回目の「N88-日本語BASIC(86)」は、調べたところ、「PC-9801 UV2」に付属していた古いバージョン(Ver.3.1)の BASIC でした。

そして落札できたこの 2回目のほうは「PC-9801 UX21」という機種に付属していた割と新しいほうのバージョン(Ver.5.0)ということで、こっちを買えて良かったです。

同じ「N88-日本語BASIC(86)」でも、バージョンによって、漢字変換方式が古いタイプだったり、一部の機能がなかったりして、やや不自由なところがあるので、同じように購入を考えているレトロPC愛好家の方は 違いを知っておいたほうが良いかもしれません。(こちらのサイトさんが N88-BASIC のバージョンの違いをまとめてくれています)


▼落札した N88-日本語BASIC(86)を起動したところ
▼付属のトレーニングディスクを起動したところ
▼当初の目的のサンプルプログラム(機械語)の実行ができた

※この画面は BASIC の MON コマンド(モニタモード)です。


「N88-日本語BASIC(86)」のディスクを エミュレーターで読ませるには?

N88-日本語BASIC(86)のディスクは たとえ3モード FDD を使っていても Windows で読み込ませることは出来ません。

(N88-BASIC のディスクはディスクのサーフェス0(表面)のトラック0番だけ128バイト/セクタで他は256バイト/セクタという特殊なディスクフォーマットを使っているから、かもしれません)

「P.D.P.」というフリーソフトウェアを使って、98実機上でN88-日本語BASIC(86)のディスクを1つのファイルへ変換する必要があります。その手順をここに書いておきます。

必要なもの:

手順:

  1. Windows にて、Vector から「P.D.P.」というソフトウェアをダウンロードし、lzh形式の圧縮をフリーウェアの Lhasa などを使って解凍します。
  2. 解凍したファイルのうち、
    PDP.COM
    PDP98.DEF
    PDP98.EXE
    の3つを 98フォーマット(MS-DOS 1.25MB)のフロッピーディスクへコピーします。
    以後これを PDP ディスクと呼びます。
  3. PC-98 実機にて、MS-DOS を起動します。
  4. PC-98 実機にて、次のようにディスクをセットします。
    Aドライブ: PDP ディスク
    Bドライブ: N88-日本語BASIC(86)システムディスク (必ずライトプロテクトをかけておくこと)
  5. MS-DOS のコマンドラインから次のように入力し、最後に RETURN キーを押します。
    A>PDP C B: DISK1.IMG
    (PDPの「C」というコマンドを実行。B:ドライブのフロッピーディスクをAドライブのDISK1.IMGというファイルへ変換する、という意味)
    すると、
    Drive B: Ready ? (Yes/No)
    とドライブの準備は OK かと聞いてくるので OK なら Y キーを押します。
    すぐに
    Disk Type : 05.OASYS 2HD (Yes/Next)
    と聞いてくるので、OASYS ではないので N を押し、つづいて
    DiskType : 06.N88-BASIC 2HD (Yes/Next)
    と聞いてくるので、N88-BASIC 2HD なので、Y を押します。
    すると、Bドライブの「N88-日本語BASIC(86)システムディスク」を読みつつ、Aドライブのファイルへ書き込みを始めます。
    カッタンカッタンと音を立てて。
    終わると、
    done.
    Aドライブに DISK1.IMG というファイル(ディスクイメージファイル)が作られます。
    (DISK1.CTL というファイルも作られますがあまり使いません)
    (1MB ほどの容量のディスクを同じ 1MB のディスク上のファイルへ変換するので、容量オーバーするのでは?と思いましたが大丈夫でした)
  6. PDP ディスクを 98本体から取り外し、Windows に接続した3モード FDD に挿入します。
  7. Windows にて、VFIC(Virtual Floppy Image Converter、リンクは Vector)を起動します。
    「出力形式 D88形式」に設定します。(NekoProject 以外のエミュレーターソフトなら別の形式を選択します)
    PDP ディスク内に作られたディスクイメージファイル DISK1.IMG を Windows 上のどこかのフォルダへコピーし、それを VFIC の画面へドラッグアンドドロップします。
    すると、DISK1.D88 というファイルが作成されます。
  8. 出来上がった DISK1.D88 を NekoProject2 (98エミュレーターソフト)で読ませれば、N88-日本語BASIC(86)が起動されます。

    ※N88-日本語BASIC(86)のユーティリティディスクも使う場合は、PDPディスクから2つのファイル DISK1.IMG と DISK1.CTL を削除してこの手順の4番から(Bドライブにユーティリティディスクを入れて)繰り返します。


マウス競り落とし

98用のマウスもちょっと気になっていました。

「98ハードに強くなる本II」でちょっとだけマウスについて触れているんです。

そこだけ試せないのは嫌だなぁと思って。

というわけで PC-9801 用マウス(リンクはヤフオクのその商品ページ)も競り落としました。(1,500円+送料1,300円)

…といってもだいぶ汚れていてガラクタ色が強く、だれも欲しがらないようで、入札は例によって私だけでした。

私が10代のころに使っていたマウスと同じ形のマウスなので、これがいいと。

届いたら洗浄して使うつもりです。壊れていなければいいんだけど…。

2021年11月25日(木)追記:

98用マウス届きました。オークションの写真の通りボロボロに汚れていました。

動作を確認したところ、左ボタンが反応しませんでした。

そこで中を開いて基板上のボタンをイソプロピルアルコールを使って洗浄したところ、ちゃんと反応するようになりました。

よかった…!!

▼左:最近の無線マウス、右:届いた品物(たぶん30年前のもの)を洗浄したところ

これで98のプログラミングでマウスを使うことができます!


それにしても、中を見たら、面実装のチップ部品で作られていましたが、ものすごいイモハンダの嵐で…。

昔はそれでも通用したんだな、と思いました。


フロッピーディスクの不幸

当初購入したフロッピーディスク10枚では足りなくなってきたので、Amazon の通販で追加で購入しました。

フロッピーディスク10枚セット(2,848円)(リンクはAmazon)

他の商品よりも安く、98フォーマット済みでもあったので、良いかなと思いました。

本当は当初購入したものと同じものを買おうとしたんですが、なぜか Amazon でエラーメッセージが出て買えなかったんです。

届いてさっそく雑用があったので使い始めましたが、いきなりディスクエラー!

フロッピーのシャッターを開けてみると、キズが!

もしやと思って他のディスクを確認すると! 10枚! すべてが!


▼開封直後なのに妙なアザ
▼これはなんだ?


汚れを拭き取れば…と思ってイソプロピルアルコールで拭いてみましたが、一部の汚れはディスクの面上にこびりついていて取れませんでした。

また面全体が変なムラがあって、どうにも使えそうにありません。カビてるんじゃないか?

この状態のまま使うと、ヘッドが汚れをかき回して、ディスクの面とヘッドも傷つけてしまうので、10枚とも廃棄処分するのが正しいと思いました。


たぶん、店舗での保存状態が悪かったんだと思います。

98フォーマットのディスクは、よくよく考えれば

1997年10月、NECはPC/AT互換機といえるPC97ハードウェアデザインガイド準拠マシンのPC98-NXシリーズを発表し、一般市場におけるPC-98は事実上その使命を終えた。

と Wikipedia にあるので、現在は 2021 年ですから、24 年前に PC-9801 本体が市場から姿を消しているんですね。

そのフロッピーディスクなので、少なくとも 20 年以上前に生産されたものなんでしょう。

赤ちゃんが生まれて成人するまでのあいだですから、その間の保管が悪ければ、上図写真のようになっていてもおかしくないでしょう。

汚れの他にホコリもなぜか内部にちらほらとありましたが、たぶんディスク内部の布地が カビ か何かが原因で劣化して、ほつれたんでしょう。

新品フロッピーディスクのこの症状は、話には聞いていたので、自分も同じ目にあったか……!という感じです。

古いものなので仕方ないと思い、通販返品の申請はしませんでした。めんどうだろうし。


私は低所得なのであって、あまりポンポンお金は出せないんですが、このまま足踏みしていてもしょうがないので、さらに Amazon で 10 枚買っておきました。

買ったのは、当初購入したフロッピーディスク10枚(リンクは Amazon)です。

なぜかエラーメッセージは出なくなっていました。なぜっ、なぜなんですかー! ほんとに


 9 月から始めたレトロ PC 関連の全出費

必要だ、必要だ、自己啓発だから OK だ、と言いながら次々と買ってきたので、集計するのが怖いんですが…

購入日商品名状態金額
2021/9/16 PC-9801 UV11(レトロPC本体) 良好 10,000円+送料 1,750円 = 11,750円
2021/9/16 MS-DOS 良好 5,600円+送料 520円 = 6,120円
2021/9/21 3モードFDD NG (送料無料)  2,980円 - ポイント115円 = 2,865円
2021/9/21 ディスプレイコネクタ 変換アダプタ 良好 (送料無料)  825円
2021/9/21 フロッピーディスク10枚 良好 (送料無料)  3,280円



小計 24,840円
2021/10/9 Vzエディタ NG 2,000円 + 送料210円 = 2,210円
2021/10/27 3モードFDD 買いなおし 良好 (送料無料)  3,380円 - ギフト券2,900円 = 472円
2021/11/15 N88-日本語BASIC(86) 良好 (送料無料)  5,000円
2021/11/19 98用のマウス 未着 1,500円+送料1,300円 = 2,800円
2021/11/20 フロッピーディスク10枚 追加購入 NG (送料無料)  2,848円
2021/11/21 フロッピーディスク10枚 追加購入 未着  (送料無料)  3,100円



小計 16,430円



合計 41,270円

たとえ、アセンブラのスキルをアップさせるためとはいえ、これ以上は無理なんだよな…


でも、本当に必要なものはこれで全部なので、これ以上は何かハプニングがない限りは出費しないと思います。

買わないことにしているもの:

4万円でアセンブラのスキル。良い買い物だったかな?

あっ

アセンブラだ。LASM(リンクは LASM 販売元)が 16,800 円しますよ。まだ出費ありますよ?

フリーウェアの NASM(リンクは Wikipedia)は無料ですが、私は LASM の親切さ(これ とか これ(リンクは LASM 販売元))でその道に入ったから??

とか言っときながら NASM とか。


(訪問者のどんなニーズと この記事がつながるか)


青椒肉絲(チンジャオロースー)+オム

昨晩の夕飯は CookDo のモトを使って青椒肉絲を作って食べていました。

3~4人前で、一人では食べきれないので、今日のお昼ご飯に残りを食べたんですが、

ちょっと気が向いて上から玉子焼きをかぶせてみました。バサッと

チンジャオ、、オムライ スゥ

………… 一人でちょっとウケたんですよねぇ。

困ったなぁ、面白くない気がする。

青椒肉絲の「椒」は胡椒(コショウ)の字と同じですねぇ…

絲は、「糸」という字はもともと「絲」と書いていたらしく、現在の「糸」は略字だとか…

チンジャオオムライスゥは美味しかったです。


2021/11/14(日)

グリフォンモデリング

4,5か月ぶりのグリフォンモデリングです。

翼を青光りさせようとしています。

翼本体だけではこのように光らせることはできません。

翼を覆うような別の形状を重ねて、その形状をソフトグローという材質設定で光らせます。

翼自体も少し透明にして発光させています。


2021/11/5(金)

プログラムリスト表示機能のテスト

最近取り組んでいる「プログラムリスト表示機能」が出来上がってきたのでそのテストです。

テストとして私が作成中の RPG のプログラムリストを表示させてみます。

「ユーザ時間」など時間の表示はサーバー側での Per lスクリプトの処理時間です。

↓↓↓

 ← 押すと切り替えます RPGのプログラム
私が開発しているRPGのプログラムです
/* memo.

名前のすみわけ(理想であって、実際はそうなっていないかもしれない)

"要素"

配列の要素 element

画面の要素 element → craft

"イベント"

イベントドリブンのイベント イベント という

シナリオスクリプトのイベント ストーリー という


"ストーリー"

以下のような、その場所でどんな行動をとったらこの関数、という構造のオブジェクトをstoryと呼ぶ。

{

"walk" : function() {...},

"outer.goton" : function() {...},

}

storyの配列をstoriesと呼ぶ。

storyを構成する story.walk や story[ "outer,goton" ] をshortStoryと呼ぶ。


"アイテム"

もちもの トレジャー

メニュー項目 アイテム


キャラの状態

直近の状態 status 毒を受けたとか

恒常的な状態 property 力がどれだけあるとか


座標

グラフィクス gx, gy, gw, gh

マス目のある画面 col, row, cols, rows

データ上の位置 x, y, w, h

マス目 1マスの名称 座標

画面のマス目 tile style.gridCol,style.gridRow

要素内に作成したマス目 inner cell col,row


配列変数の名前と、同じデータを持つ連想配列変数の名前

配列 -s たとえばcharacters

連想配列 -z たとえばcharacterz


そのとき、たとえばsprites、spritezの読み方について

配列時 sprites スプライトス

連想配列時 spritez スプライトス

※どちらも-スと読む。つまり-zは見た目の区別でしかない


また、childの複数形childrenについて -z を表現できないので、

childs, childz とする。

連想配列のkeyとキー入力のkey

連想配列 name

キー入力 key

言葉の意味

tweak 調整する

strategy 広い視野で物事を見て全体の方向性を決めていく

tactic 実務レベルでどのように対処するのかという部分的な一つの計画


async関数とawait


awaitはasync関数を暗黙的に分割します。


async test() {

処理1

await 関数など

処理2

}

処理1までは通常の関数と同じように動作し、

awaitの後の処理2はawaitの行が終わる(= Promiseの解決が履行される)までは実行されません。

また、処理2が終わった後は、test関数が呼び出しされた場所には戻りません。

test関数の最初のawaitの時点で、プログラムは2つ(並列)に枝分かれします。

片方はtest()を出てtest()を呼び出した次の行へ進み、もう片方はawait以降を実行します。

await以降を実行した後は、どこにも戻らず、終了します。

このRPGのプログラムにおいては、枝分かれすることはあまり価値がありません。

1つの関数を途中で止めることができるawaitに価値があります。

*/


/*

----- ----- -- --

12345 12345 12 12

45 3 2\n


----- ----- -- --

12345 12345 12 12

45 3 2

*/


let str = 'Twas\' the night before Xmas...';

let str = "Twas\" the night before Xmas...";

let newstr = str.replace(/xmas/i, 'Christmas');

console.log(newstr); // Twas the night before Christmas...

let re = /(\w+)\s(\w+)/;

let str = 'John Smith';

let newstr = str.replace(re, '$2, $1');

console.log(newstr); // Smith, John

function f2c(x) {

function convert(str, p1, offset, s) {

return ((p1 - 32) * 5/9) + 'C';

}

let s = String(x);

let test = /(-?\d+(?:\.\d*)?)F\b/g;

return s.replace(test, convert);

}



text = "a;b;c";

/*

abc = /DEF/ig;

*/

text.split( /;/ );

re = /[0-9]+/;

re = /DEF/gi;

reg = /[a-z]+/i;

var re = /123/g;

var str = 'abc123def';

re.test(str);

var re = /123/g;

re.test("abc123def");

var str = "2019年12月31日";

var result = str.match(/(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/u);

console.log(result.groups.year); // 2019

console.log(result.groups.month); // 12

console.log(result.groups.day); // 31

console.log(RegExp.input); // => "abc123def"

re.test("abc456def"); // マッチしないので index は変化しない

console.log(RegExp.input); // => "abc123def"

"12:34:56".match(/(\d+):(\d+):(\d+)/);

console.log(RegExp.$1); // => 12

console.log(RegExp.$2); // => 34

console.log(RegExp.$3); // => 56

console.log(re.lastIndex); // => 6

"abc123def".match(/(123)/);

console.log(RegExp.lastMatch); // => 123

console.log(RegExp.leftContext); // => abc

console.log(RegExp.rightContext); // => def

console.log(RegExp.lastParen); // => 123

var re = /A[0-9]+/g;

"abc".match(/ABC/) // マッチしない

"abc".match(/ABC/i) // マッチする;

"123\n456\n789".match(/^456/) // マッチしない

"123\n456\n789".match(/^456/m) // マッチする

res = "12:34:56".match(/\d+/g);

"🍔".match(/^.$/) // 2文字とみなすのでマッチしない

"🍔".match(/^.$/u) // マッチする

reg = /A/y;

for (var m of "2020-12-31".matchAll(/[0-9]+/g)) {

console.log(m);

}

if( text.match( /(.*");(.*);(.*)/ ) ) {

alert( RegExp.$1 + ", " + RegExp.$2 + ", " + RegExp.$3 );

}


// |

// V

//---■■■ 1 Utl ■■■

class Utl {//c

static arrayForward( array, element ) {//m

//配列内の要素を末尾に置く


let idx = array.indexOf( element );

//check.

if( idx == -1 ) {

alert( "Utl.arrayForward() 失敗した" );

return;

}


array.splice( idx, 1 );

array.push( element );

}

static accessBy( /**/ ) {//m

//test.test2.test3のようなアクセスで、test2がnullだとエラーで止まる。

//accessBy( test, "test2", "test3" )とすれば止まらず、nullを返す。

//try,catchもあるが、なじみがないので。

let array = Array.from( arguments );

let object = array.shift();

do {

if( object === null ) return null;

if( object === undefined ) return undefined;

object = object[ array.shift() ];

} while( array.length );

return object;

}

static array2object( array, object, keyMaker ) {//m

//配列を連想配列に変換する。その際の名前はkeyMakerが作る

//objectは作成済みの場合

for( let element of array ) {

object[ keyMaker( element ) ] = element;

}

}


static objectFrom( array, keyMaker ) {//m

//配列を連想配列に変換する。その際の名前はkeyMakerが作る

//objectは作成済みではない場合

let object = new Object();

for( let element of array ) {

object[ keyMaker( element ) ] = element;

}

return object;

}

static lengthBin( str ) {//m

//漢字を2バイト、半角を1バイトとしてバイト数を計算 (UTF8を前提)

return encodeURI( str ).replace( /%20/, "*" ).replace( /%..%..%../g, "**" ).length;

}

static lengthBinDiv2( str ) {//m

//lengthBinの結果を2で割り、小数点切り上げ

return Math.ceil( Utl.lengthBin( str ) / 2 );

}

static maxLength( strArray ) {//m

//文字列配列中で、一番長い文字列の文字数を返す

let v = 0;

for( let str of strArray ) v = Math.max( v, String( str ).length );

return v;

}

static maxLengthBin( strArray ) {//m

//そのバイト版

let v = 0;

for( let str of strArray ) {

v = Math.max( v, Utl.lengthBin( String( str ) ) );

}

return v;

}

static maxLengthBinDiv2( strArray ) {//m

//その、2で割り、小数点切り上げ版

return Math.ceil( Utl.maxLengthBin( strArray ) / 2 );

}

static copyObjectKai( object, history ) {//m

//オブジェクトを厳密にコピーするの改造版

//check. 最初の呼び出し

if( history == undefined ) {

history = {

origin : new Array(),

copy : new Array(),

}

}

if( object instanceof Object ) {

//check. すでにコピーしたことがある(無限ループ検知)

if( history.origin.includes( object ) ) {

let idx = history.origin.indexOf( object );

return history.copy[ idx ];

}

history.origin.push( object );

let copy;

if( object instanceof Array ) {

copy = new Array();

history.copy.push( copy );

for( let i = 0; i < object.length; i++ ) {

copy[ i ] = Utl.copyObjectKai( object[ i ], history );

}

} else {

copy = new Object();

history.copy.push( copy );

for( let name in object ) {

//改造箇所はここだけ

//オブジェクトのメンバでcopy:trueを持つ場合に限り

//厳密にコピーをする。

//copy:trueがないと、コピー元のオブジェクトの参照になる。

//モンスターやトレジャーはカタログをコピーするが

//HPや使用回数だけはカタログへの参照ではなくコピーにしたい。

if( object[ name ].copy ) {

//HPや使用回数はこちら

copy[ name ] = Utl.copyObjectKai( object[ name ], history );

} else {

//その他定義は読むだけなので参照でよい

copy[ name ] = object[ name ];

}

}

}

return copy;

} else {

return object;

}

}

}//Utl

//---■■■ 2.5 Drawing ■■■


class Drawing {//c

constructor() {//m

this.x = 0;

this.y = 0;

this.w = 16;

this.h = 16;

this.afterEffects = new Array();

this.beforeEffects = new Array();

}

effectDraw1( cc ) {//m

cc.save();

// cc.fillStyle = this.style.backgroundColor;

// cc.fillRect( 0, 0, this.w, this.h );


cc.translate( this.centerX, this.centerY );


for( let i = 0; i < this.beforeEffects.length; i++ ) {

let effect = this.beforeEffects[ i ];

if ( effect.call( this, cc ) ) this.beforeEffects.splice( i--, 1 );

}

cc.translate( -this.centerX, -this.centerY );

}

effectDraw2( cc ) {//m

for( let i = 0; i < this.afterEffects.length; i++ ) {

let effect = this.afterEffects[ i ];

if ( effect.call( this, cc ) ) this.afterEffects.splice( i--, 1 );

}

cc.restore();

}

draw( cc ) {//m


}

}


//---■■■ 2 Craft ■■■

/*

Craft を extends しているもの

class App

class MessageWindow

class List

class Menu extends List

class TitleScreen

class MapScreen

class BattleView


*/

class Craft extends Drawing {//c

constructor( style, app ) {

super();

this.app = app ? app : this;

//check.

if( this == app && style.gridCellSize == undefined )

alert( "appに限り、styleにgridCellSizeの定義が必要です。" );


//デフォルト値と上書き

this.style = {

gridCol : 0,

gridRow : 0,

gridCols : 5,

gridRows : 5,

backgroundColor : "black",

};

this.assignStyle( style );


this.name = this.constructor.name;

this.childs = new Array();

this.prevFocus = null;


}

assignStyle( newStyle ) {

Object.assign( this.style, newStyle );

this.x = this.style.gridCol * this.app.style.gridCellSize;

this.y = this.style.gridRow * this.app.style.gridCellSize;

this.w = this.style.gridCols * this.app.style.gridCellSize;

this.h = this.style.gridRows * this.app.style.gridCellSize;

this.centerX = this.w / 2;

this.centerY = this.h / 2;

}

typeKey( key ) {//m

}

senseKey( key ) {//m

}


draw( cc ) {//m

this.drawWindow( cc );

this.drawChildren( cc );

}

drawWindow( cc ) {//m


//ウィンドウ

let x = - this.app.style.gridCellSize / 2;

let y = - this.app.style.gridCellSize / 2;

let w = this.w + this.app.style.gridCellSize;

let h = this.h + this.app.style.gridCellSize;


cc.fillStyle = "black";

cc.fillRect( x, y, w, h );


//ウィンドウ 面

cc.fillStyle = "white";

cc.fillRect( x, y, w, h );


//ウィンドウ 枠

if( this.app.currentCraft == this ) {

cc.strokeStyle = "yellow";

cc.strokeRect( x + 1, y + 1, w - 2, h - 2 );

}

cc.strokeStyle = "black";

cc.strokeRect( x, y, w, h );

}

drawChildren( cc ) {//m

for( let child of this.childs ) {

cc.save();

cc.translate( child.x, child.y );

child.draw( cc );

cc.restore();

}

}

searchStory() {//m

}

focus() {//m

//check. すでにフォーカスされている。

if( this.app.currentCraft == this ) {

console.log( "already", this.name, "focused" );

return;

}


if( this.parent )

console.log( "focus", this.name, "at", this.parent.name );

else

console.log( "focus", this.name );


if( this.prevFocus == undefined )

this.prevFocus = this.app.currentCraft;

this.app.currentCraft = this;


//前面に移動

if( this.parent ) {

let idx = this.parent.childs.indexOf( this );

this.parent.childs.splice( idx, 1 );

this.parent.childs.push( this );

}

}

appendChild( child, nofocus ) {//m

this.childs.push( child );

child.setParent( this );

if( ! nofocus ) child.focus();

return child;

}

appendFloat( child ) {

this.appendChild( child, true );

}

setParent( parent ) {

this.parent = parent;

//check.

for( let name in this.style ) {

if( this.style[ name ] == "inherit" ) {

this.style[ name ] = parent.style[ name ];

}

}

}

removeChild( child ) {//m

console.log( "remove", child.name, "from", this.name );

let idx;


if( ( idx = this.childs.indexOf( child ) ) > -1 ) {

this.childs.splice( idx, 1 );

if( child.prevFocus ) child.prevFocus.focus();

} else {

alert( "警告:正体不明のオブジェクトをcraft.removeChild()しようとしました." );

}


return child;

}

removeAllCrafts() {//m

for( let child of this.childs ) {

child.removeAllCrafts();

}

this.parent.removeChild( this );

}


}//Craft

// |

// V

//---▼▼▼ 3 App ▼▼▼

class App extends Craft {//c

constructor( canvasId ) {

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


let canvas = document.getElementById( canvasId );

let cc = canvas.getContext( "2d", { alpha : false } );


window.LA = new LogicAnalyzer( cc, {

titleFontSize : 5,

commentFontSize : 5,

horizontalDiv : 16,

trigChart : "'exit' story",

horizontalShift : 132,

}, true );


let logiczoom = 1;

let canvasWidth = 256 / logiczoom; //プログラム上の論理的サイズ

let canvasHeight = 224 / logiczoom;

canvas.width = canvasWidth;

canvas.height = canvasHeight;

if( 0 ) canvas.style.imageRendering = "crisp-edges"; //アンチエイリアス


/* 解像度変更機能:


mozaic canvasWidth canvas.width scale

0.5 256 2048 8 さらにきめ細かい

1 256 1024 4 きめ細かい(標準)

2 256 512 2 少し荒い

4 256 256 1 ファミコン風

*/

if( 1 ) {

let mozaic = 1; //見た目の粒の大きさ

canvas.width = canvasWidth / mozaic * 4; //物理的

canvas.height = canvasHeight / mozaic * 4;

cc.scale( 4 / mozaic, 4 / mozaic ); //物理1ピクセル当たりの論理ピクセル数

}


let gridCellSize = canvasWidth / 32;


super( {

gridsPerInnerCell : 2, //内部1セルあたり、何gridか。

//gridはアプリ内での絶対的なマス目。

//例えば、gridが8ピクセルで、マップチップが16ピクセルなら、チップ当たり2grid

//という意味。

gridCol : 0,

gridRow : 0,

gridCols : Math.floor( canvasWidth / gridCellSize ),

gridRows : Math.floor( canvasHeight / gridCellSize ),


gridCellSize : gridCellSize, //全画面共通の最小単位。

innerCellSize : gridCellSize * 2, //その画面の単位。マップチップ等

}, null );


this.cc = cc;

this.cc.canvasWidth = canvasWidth;

this.cc.canvasHeight = canvasHeight;


this.effectz = new Effectz( this );

}

// |

// V

async initialize() {//m

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


this.message = null;


//---メトロノーム

this.metronomez = new Object();


this.addMetronome( "t1000_i2" );

this.addMetronome( "t500_i2" );

this.addMetronome( "t250_i2" );

//カーソル専用(手動追加)

this.metronomez.forCursor = {

maxTime : 500,

maxIndex : 0,

time : 0,

index : 0,

toggle : true,

changed : false,

}

/*

メトロノームの各フラグの変化タイミング図解(タイミングチャート)

maxTime : 250,

maxIndex : 3, の場合の各フラグの動き↓


(経過時間) 0 ... 250 ... 500 ... 750 ... 1000 ...(ms)


time : 0, 0 ..249 0 ..249 0 ..249 0 ..249 0 .. ...(ms)


index : 0, 000000001111111122222222000000001111 ...


toggle : true, ~~~~~~~~________~~~~~~~~________~~~~ ...


changed : false, ________~_______~_______~_______~___ ...


~ : true

_ : false

... : 略

※このグラフ中の1文字単位の縦1列はrequestAnimationFrameに相当する


つまり、requestAnimationFrameごとに、

timeは経過時間を増やしていきmaxTimeになると0に戻る

indexはtimeがmaxTimeになるごとに1増え、maxIndexになると0に戻る

toggleはtimeがmaxTimeになるごとに真偽を反転する

changedはtimeがmaxTimeになったときだけ真になる


maxTimeでアニメのパラパラのスピード決め

toggleやchangedでアニメのパラパラの実行を判断

indexでアニメのパラパラの画の切り替え

*/


this.spritez = {

player : {

images : [ "←", "-", "↑", "|", "→", "-", "↓", "|" ],

metronome : this.metronomez.t500_i2,

},

}


this.artz = {

spritez : this.spritez,

bgms : new Object(),

soundz : new Object(),

imagez : new Object(),

}

//---素材のプリロード

let dir = location.pathname.replace( /%20/g, " " ).replace( /\/[a-z0-9_ .-]*$/i, "" ) + "/";


this.cc.fillStyle = "cyan";

let str = "want a moment"

this.cc.fillText( str, ( this.cc.canvasWidth - this.cc.measureText( str ).width ) / 2, 100 );


await Promise.all( [

this.load( "bgm", "field", dir + "_bgm/街/MusMus-CT-NV-24/MusMus-CT-NV-24.mp3" ),

this.load( "bgm", "town", dir + "_bgm/街/MusMus-BGM-078/MusMus-BGM-078.mp3" ),

this.load( "sound", "pushA", dir + "_sound/pushA.mp3" ),

this.load( "sound", "pushWall", dir + "_sound/pushWall.mp3" ),

this.load( "sound", "walk", dir + "_sound/walk.wav" ),

this.load( "sound", "damage", dir + "_sound/musmus_other_set/othr10.mp3" ),

this.load( "image", "slime", dir + "_img/slime.png" ),

this.load( "image", "battleBG", dir + "_img/battle.png" ),

] );

console.log( "素材のロード完了." );


this.monsters = new Array(); //戦闘中のモンスター

this.characters = new Array();

this.characterz = new Object();

this.treasureCatalog = new Object();


//---■メニュー

this.menusrcs = [

{

name : "yesno",

title : "",

items : [

{

name : "はい",

},

{

name : "いいえ",

}

],

},

{

name : "camp",

title : "コマンド?",

argzName : "cmd",

items : [

{

name : "どうぐ",

title : "だれの?",

items : this.characters,

argzName : "char1",

assistMenu : {

title : "どれ?",

argzName : "dougu",

row : 1, //だれでも表示高さを固定して表示

},

},

{

name : "しらべる",

},

{

name : "まほう",

title : "だれの?",

items : this.characters,

argzName : "char1",

},

],

},

//---

{

name : "つかう",

title : "だれに?",

items : this.characters,

argzName : "char2",

story : async function( argz ) {

this.openMessage();

await this.message.write( argz.char1.name + "は" + argz.char2.name + "に" + argz.dougu.name + "を使った!#k" );

let lostFlg = true;

if( argz.dougu.property.limit != undefined ) {

//limitが0になったらなくなるタイプ

argz.dougu.property.limit --;

//check.

lostFlg = argz.dougu.property.limit == 0;

if( lostFlg ) {

this.message.clear();

await this.message.write( "なんと " + argz.dougu.name + " は壊れてしまった!#k" );

}

}

if( lostFlg ) {

let idx = argz.char1[ argz.cmd.name ].indexOf( argz.dougu );

argz.char1[ argz.cmd.name ].splice( idx, 1 );

}

this.closeMessage();

},

},

{

name : "わたす",

title : "だれに?",

items : this.characters,

argzName : "char2",

story : async function( cmd, char1, dougu, cmd2, char2 ) {

this.openMessage();

await this.message.write( char1.name + "は" + char2.name + "に" + dougu.name + "を渡した!#k" );

this.closeMessage();

},

},

{

name : "すてる",

story : async function( cmd, char1, dougu, cmd2 ) {

this.openMessage();

await this.message.write( char1.name + "は" + dougu.name + "を捨てた!#k" );

this.closeMessage();

},

},

]

this.menusrcz = Utl.objectFrom( this.menusrcs, element => element.name );

//---■トレジャー

this.treasureCatalog = {

"どうけん" : {


},

"せいどうけん" : {


},

"てっけん" : {


},

"こうてつけん" : {


},

"ひほう" : {

property : {

copy : true,

limit : 3,

},

assistMenuCamp : {

title : "どうする?",

argzName : "dousuru",

items : [

this.menusrcz[ "つかう" ],

this.menusrcz[ "わたす" ],

this.menusrcz[ "すてる" ],

],//items

},

},

"なんこう" : {

property : {

copy : true,

},

assistMenuCamp : {

title : "どうする?",

argzName : "dousuru",

items : [

this.menusrcz[ "つかう" ],

this.menusrcz[ "わたす" ],

this.menusrcz[ "すてる" ],

],//items

},//nextMenu

assistMenuBattle : this.menusrcz[ "つかう" ],

},//なんこう

}//treasureCatalog


//tweak. キーを名前として要素内に登録

for( let name in this.treasureCatalog ) {

this.treasureCatalog[ name ].name = name;

}


//---■戦闘思考ルーチン

this.tacticz = {

slime : function( ourselves, enemies, battleLog ) {

/*

result = {

menus

argz

story

selectedItem

}

*/

return {

argz : {

enemy : enemies[ 0 ],

},

story : this.battleStories[ "たたかう" ],

}

},

}

//---■モンスター

this.monsterCatalog = {

"モモスラ" : {

image : this.artz.imagez.slime,

property : {

copy : true,

hp : 10,

atack : 5,

defence : 5,

},

tactic : this.tacticz.slime,

}

}

//tweak. キーを名前として要素内に登録

for( let name in this.monsterCatalog ) {

this.monsterCatalog[ name ].name = name;

}


//---■戦闘コマンド 実行


this.battleStories = {

"たたかう" : async function( argz ) {

this.openMessage();

this.message.clear();

await this.message.write( argz.self.name + "は " + argz.enemy.name + " にこうげき!#n" );

//check.

if( argz.enemy.property.hp == 0 ) {

await this.message.write( "しかし" + argz.enemy.name + "はすでに倒れている!#k" );

return;

}


let enemyDamage = argz.self.property.atack;

enemyDamage -= argz.enemy.property.defence;


if( enemyDamage > 0 ) {


for( let i = 0; i < 4; i++ ) {

argz.enemy.flg = ! argz.enemy.flg;

await this.delay( 100 );

}


await this.message.write( argz.enemy.name + "に " + enemyDamage + " ポイントのダメージをあたえた!#k" );

argz.enemy.property.hp -= enemyDamage;

//check.

if( argz.enemy.property.hp <= 0 ) {

argz.enemy.property.hp = 0;

await this.message.write( "#n" + argz.enemy.name + "は倒れた!#k" );

}

} else {

await this.message.write( "ミス!ダメージをあたえられない!#k" );

}

}

}


//---■戦闘コマンド

this.batcomz = {

"たたかう" : {

title : "あいては?",

items : function() {

return this.monsters.filter( m => m.property.hp > 0 );

},

argzName : "enemy",

row : 1,

story : this.battleStories[ "たたかう" ],

},

};

//tweak. キーを名前として要素内に登録

for( let key in this.batcomz ) {

this.batcomz[ key ].name = key;

}


//---■キャラクター


this.characters.push( {

name : "キャラ1",

property : {

hp : 20,

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "つるぎ" },

this.treasureCatalog[ "なんこう" ],

Utl.copyObjectKai( this.treasureCatalog[ "ひほう" ] ),

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );


this.characters.push( {

name : "キャラ2",

property : {

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "なんこう2" },

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );

this.characters.push( {

name : "キャラ3",

property : {

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "なんこう3" },

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );

Utl.array2object( this.characters, this.characterz, element => element.name );


}//initialize()

start() {//m

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


//------------


this.focus();

this.keys = new Array();

this.keyLocked = false;


this.memoryCard = {

player : {

position : {

map : App.worldmap,

x : 0,

y : 0,

},

direction : 0,

gold : 200,

},

}


//タイトル画面から始める

let titleScreen = new TitleScreen( {

innerCellSize : this.style.gridCellSize * 2,

gridCol : 0,

gridRow : 0,

gridCols : this.style.gridCols,

gridRows : this.style.gridRows,

}, this );

this.appendChild( titleScreen );



this.startFrame();


}//start()

// |

// V

stop() {//m

}

// |

// V

startFrame() {//m

this.keys.length = 0;

this.beforeTime = 0;

this.frame( 0 );

onkeydown = this.onkeydownx.bind( this );

onkeyup = this.onkeyupx.bind( this );

}

// |

// V

stopFrame() {//m

cancelAnimationFrame( this.timerId );

this.keys.length = 0;

onkeydown = null;

onkeyup = null;

}


// |

// V

//プリロード

load( type, name, src ) {//m

return new Promise( function( promiseOk ) {

let object;

console.log( type, name, "loading.." );

switch( type ) {

case "image":

object = new Image();

this.artz.imagez[ name ] = object;

object.onload = function() {

console.log( "\t", type, name, "loaded." );

promiseOk();

}

object.src = src;

break;

case "bgm":

object = new Audio();

object.oncanplaythrough = function() {

this.artz.bgms[ name ] = object;

object.oncanplaythrough = null;

console.log( "\t", type, name, "loaded." );

promiseOk();

}.bind( this );

object.src = src;

object.load();

break;

case "sound":

object = new Audio();

object.oncanplaythrough = function() {

this.artz.soundz[ name ] = object;

object.oncanplaythrough = null;

console.log( "\t", type, name, "loaded." );

object.rapidPlay = function() {

object.pause();

object.currentTime = 0;

object.play();

/*

同じ音楽ファイルの連続演奏時は

最初にリセットをかける必要がある。

rapid=急速

*/

}

promiseOk();

}.bind( this );

object.src = src;

object.load();

break;

}

}.bind( this ) );

}//load()


onkeydownx( e ) {//m


//debug. 現在の階層構造を表示

if( e.which == 79 ) { //'o'キー

console.log( "=== 階層構造 ===" );

console.log( this.debug_dir( this ) );

console.log( "=== /階層構造 ===" );

return;

} else if( e.which == 49 ) { //'1'キー

console.log( "=== メトロノームリスト ===" );

this.debug_metronome( this );

console.log( "=== /メトロノームリスト ===" );

return;

} else if( e.which == 69 ) { //'e'キー

console.log( "=== エフェクトリスト ===" );

this.debug_effect( this.mapScreen );

console.log( "=== /エフェクトリスト ===" );

} else {

// console.log( e.which );

}


if( this.keys.indexOf( e.which ) == -1 ) {


this.keys.push( e.which ); //キーセンス オン


this.currentCraft.typeKey( e.which );

//キータイプ 実行

}

}

// |

// V

onkeyupx( e ) {//m

let idx = this.keys.indexOf( e.which );

if( idx > -1 ) this.keys.splice( idx, 1 );

}


//---汎用的メソッド(小道具)※名前にとつけると大げさになるもの

flushKey() {//m

this.keys.length = 0;

}


//---汎用的メソッド(ユーティリティ)

addMetronome( name /*other args*/ ) {//m

//オーバーロード(メソッド名が同じで引数のパターンが異なる)の模造

//”関数の先頭で引数の型を判定する条件分岐で対応”

let argsId = "";

for( let arg of Array.from( arguments ) ) {

argsId += "," + ( typeof arg ).substr( 0, 1 );

}

let metronome;

switch( argsId ) {

case ",s" : //フォーマット名称 t時間_iインデックス上限

let tokens = name.split( /_/ );

metronome = {

maxTime : Number( tokens[ 0 ].substr( 1 ) ),

maxIndex : Number( tokens[ 1 ].substr( 1 ) ),

time : 0,

index : 0,

toggle : false,

changed : false,

}

this.metronomez[ name ] = metronome;

break;

case ",s,n" : //自由な名称、時間

let tm = arguments[ 1 ];

metronome = {

maxTime : tm,

maxIndex : 0,

time : 0,

index : 0,

toggle : false,

changed : false,

}

this.metronomez[ name ] = metronome;

break;

default:

alert( "addMetronome()にて、未定義の引数リスト:[" + args + "]" );

}

return metronome;


}//addMetronome()

// |

// v

deleteMetronome( metronome ) {//m

delete this.metronomez[ metronome.name ];

}


// |

// V

frame( tm ) {//m



//経過時間

let diff = tm - this.beforeTime;

this.beforeTime = tm;


//メトロノーム更新

for( let metronome of Object.values( this.metronomez ) ) {

metronome.time += diff


//check. そのメトロノームは時を刻んだ

if( metronome.time >= metronome.maxTime ) {

metronome.time = 0;

metronome.toggle = ! metronome.toggle;

metronome.changed = true;

metronome.index ++;

//check. インデックスリセット

if( metronome.index == metronome.maxIndex ) metronome.index = 0;

} else {

metronome.changed = false;

}

}


//キーセンス 処理

for( let i = this.keys.length - 1; i >= 0; i-- ) {

let key = this.keys[ i ];

this.currentCraft.senseKey( Math.abs( key ) );

}


//1ドットスクロール実行

if( this.mapScreen )

if( this.mapScreen.scroll.isAnimating ) {

this.mapScreen.frameForScroll();

}


this.storyCheck();


this.draw( this.cc );


this.timerId = requestAnimationFrame( this.frame.bind( this ) );



}

// |

// V

async storyCheck() {//m

//ストーリー検索&実行

let stories = this.currentCraft.searchStory();

//check.

if( ! stories ) return;

for( let story of stories ) {

let res = await story.story.call( this, story.action );

//check. 真を返したら以降を打ち切り(町に入るところで戦闘発生等防ぐため)

if( res ) break;

}

}


openMessage( style ) {//m

//check. デフォルト値と上書き

style = Object.assign( {

gridCol : 2,

gridRow : 18,

gridCols : 28,

gridRows : 8,

innerCellSize : this.style.innerCellSize,

}, style );


this.closeMessage();

this.message = new MessageWindow( style, this );

this.appendChild( this.message );

}

// |

// V

closeMessage() {//m

//check.

if( ! this.message ) return;


this.removeChild( this.message );

this.message = null;

}

// |

// V


yesno( closeFlg ) {//m

this.artz.soundz.pushA.rapidPlay();


let yesnoMenu = new Menu( "selectmode", "", this.menusrcz.yesno, {

gridCol : 26,

gridRow : 25,

}, this );

this.appendChild( yesnoMenu );


return new Promise( function( promiseOk ) {

console.log( "promised 'yesno'" );

yesnoMenu.promiseOk = promiseOk;

}.bind( this ) ).then( function( result ) {

console.log( "promise 'yesno' completed" );

this.removeChild( yesnoMenu );

if( Utl.accessBy( result, "selectedItem" ) ) {

return result.selectedItem.name == "はい";

} else

return false;

}.bind( this ) );

}

// |

// V

//---●●● shop ●●●

async shop( menusrc, wordingz ) {//m

let treasureMenu = new Menu( "selectmode", "", menusrc, {

gridCol : 2,

gridRow : 2,

}, this );

this.mapScreen.appendChild( treasureMenu );


//所持金表示

let stat = new List( {

columns : [ { key : "title" }, { key : "value", align : "right" } ],

items : [

{

title : "エン",

value : function() { return this.memoryCard.player.gold; },

},

],


}, {

gridCol : treasureMenu.style.gridCols + 2,

gridRow : 0,

}, this.app );

treasureMenu.appendFloat( stat );


this.openMessage();


while( 1 ) {

//商品を選ぶ

await this.message.write( wordingz[ "何を買うんだ?" ] + "#n" );

treasureMenu.focus();

let result = await treasureMenu.select();


//check. お店から退出

if( ! result ) break;


let selectedItem = result.selectedItem;


this.message.focus(); //←見た目のため

await this.message.write( selectedItem.name + wordingz[ "だな。" ] + "#n" );

await this.message.write( selectedItem.price + "エン" + wordingz[ "だ。買うかね?" ] + "#n" );

if( await this.yesno() ) {

this.memoryCard.player.gold -= selectedItem.price;

await this.message.write( wordingz[ "まいどあり!" ] + "#n#k#n" );

}

}

await this.message.write( wordingz[ "ありがとうございました!" ] + "#k" );

this.closeMessage();

this.mapScreen.removeChild( treasureMenu );

}


encounter() {//m

this.battle();

}

// |

// V

//---●●● battle ●●●

async battle() {//m


//モンスターを用意

this.monsters = new Array();

let max = Math.floor( Math.random() * 5 ) + 1;

max = 1;

console.log( max );

for( let i = 0; i < max; i++ ) {

this.monsters[ i ] = Utl.copyObjectKai( this.monsterCatalog[ "モモスラ" ] ),

this.monsters[ i ].name += i + 1;

}

let masterOfMonsters = null;


let battleLog = new Array();


this.battleView = new BattleView( this.monsters, {

gridCol : 6,

gridRow : 6,

gridCols : 20,

gridRows : 12,

}, this );


//---開始エフェクト

this.keyLocked = true;

//フラッシュして

await this.effectz.flash( this.mapScreen, "red", Date.now() + 200 );

await this.effectz.flash( this.mapScreen, "yellow", Date.now() + 200 );

await this.effectz.flash( this.mapScreen, "red", Date.now() + 200 );

if( 1 ) {

//画面を登録

this.mapScreen.appendFloat( this.battleView );

//ローリングで画面が登場し

await new Promise( function( promiseOk ) {

let metronome = this.addMetronome( "batsta", 60 );

let step = 20;

let movl = 200;

let count = 0;

this.battleView.beforeEffects.push( function( cc ) {

let theta = Math.PI * 2 / step * count;

let alpha = 1 / step * count;

let movx = movl - movl / step * count;

cc.globalAlpha = alpha;

cc.rotate( theta );

cc.translate( this.x + movx, this.y );

//check.

if( ! metronome.changed ) {

return;

}

count ++;

//check.

if( count == step ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

} );

}.bind( this ) );

} else {

//ローリングで画面が登場し

await this.effectz.battleStart( this.battleView );

//画面を登録

this.mapScreen.appendFloat( this.battleView );

}

//モンスターのベールが明かされる

for( let i = 0; i < 5; i++ ) {

for( let monster of this.battleView.monsters ) monster.flg = ! monster.flg;

await this.app.delay( 200 );

}

this.keyLocked = false;


this.openMessage();

let m = this.monsters.map( monster => monster.name ).join( "、" );

await this.message.write( m + "があらわれたぞぞ!#k" );

this.closeMessage();


let theEndOfTheBattle;

do {

let commandz = new Object();


this.openMessage( {

gridCol : 9,

gridRow : 18,

gridCols : 22,

gridRows : 8,

} );


//プレイヤー側のコマンド決定

for( let i = 0; i < this.characters.length; i++ ) {

let character = this.characters[ i ];

//check.

if( character.property.hp == 0 ) continue;


let cmdMenu = new Menu( "selectmode", "", {

name : "batcom",

title : character.name,

items : character.batcoms,

}, {

gridCol : 1,

gridRow : 18,

}, this.app );

this.appendChild( cmdMenu );


this.message.clear();

this.message.write( "#w0" + character.name + "はどうする?" );


let command = await cmdMenu.select();

cmdMenu.removeAllCrafts();

//check.

if( ! command ) {

i -= i == 0 ? 1 : 2;

continue;

}

//tweak.

command.argz.self = character;


commandz[ character.name ] = command;

}//for character

this.closeMessage();


//モンスター側のコマンド決定

if( masterOfMonsters ) {

//リーダーが思考する場合 未使用 未デバッグ

let resultz = masterOfMonsters.strategy.call( this, this.monsters, this.characters, battleLog );

Object.assign( commandz, resultz );

} else {

//それぞれが思考する場合

for( let monster of this.monsters ) {

//check.

if( monster.property.hp == 0 ) continue;

commandz[ monster.name ] = monster.tactic.call( this, this.monsters, this.characters, battleLog );

commandz[ monster.name ].argz.self = monster;

}

}


for( let name in commandz ) {

//check.

if( commandz[ name ].argz.self.property.hp == 0 ) continue;

let command = commandz[ name ];


await command.story.call( this, command.argz );


//check. 味方の生存者いない

if( ! this.characters.some( c => c.property.hp > 0 ) ) {

await this.message.write( "#n" + characters[ 0 ].name + "たちは全滅した…#k" );

theEndOfTheBattle = true;

break;

} else if( ! this.monsters.some( m => m.property.hp > 0 ) ) {

//敵の生存者いない

await this.message.write( "#nモンスターたちを倒した!#k" );

theEndOfTheBattle = true;

break;

} else {

theEndOfTheBattle = false;

}

}

this.closeMessage();


} while( ! theEndOfTheBattle );


this.mapScreen.removeChild( this.battleView );

}

async delay( ms ) {//m

await new Promise( promiseOk => setTimeout( promiseOk, ms ) );

}

async hitanykey( test ) {//m

let bak = onkeydown;

await new Promise( promiseOk => onkeydown = promiseOk ).then( function() {

console.log( test );

} );

onkeydown = bak;

}

debug_dir( object, tab, idx ) {//m

//check.

if( tab == undefined ) tab = "";

let str = tab;

if( object != this ) {

str += "→ childs[";

str += idx;

str += "] = ";

}

str += object.name;

if( object == this.currentCraft ) str += "*";

if( object.prevFocus ) str += " (prev:" + object.prevFocus.name + ") ";

str += "\n";


tab += " ";


for( let i = 0; i < object.childs.length; i++ ) {

let child = object.childs[ i ];

str += this.debug_dir( child, tab, i );

}

return str;

}

debug_metronome() {//m

let i = 0;

for( let name in this.metronomez ) {

console.log( i++, name );

}

}

_debug_dir( object, tab, idx ) {//m

//check.

if( tab == undefined ) tab = "";

let str = tab;

if( object != this ) {

str += "→ childs[";

str += idx;

str += "] = ";

}

str += object.name;

if( object == this.currentCraft ) str += "*";

if( object.prevFocus ) str += " (prev:" + object.prevFocus.name + ") ";

str += "\n";


tab += " ";


for( let i = 0; i < object.childs.length; i++ ) {

let child = object.childs[ i ];

str += this.debug_dir( child, tab, i );

}

return str;

}


debug_effect( target ) {//m

for( let i = 0; i < target.beforeEffects.length; i++ ) {

let effect = target.beforeEffects[ i ];

console.log( "beforeEffects[", i, "]", effect.id );

}

for( let i = 0; i < target.afterEffects.length; i++ ) {

let effect = target.afterEffects[ i ];

console.log( "afterEffects[", i, "]", effect.id );

}

}

}//App

//---▲▲▲ 3 App ▲▲▲



//---◆◆◆ 4 MessageWindow ◆◆◆

//もとは、試作である。app.jsへ適合中のもの。


class MessageWindow extends Craft {//c

constructor( style, app ) {

//スタイルデフォルト値と、その上書き

style = Object.assign( {

gridCols : app.style.gridCols - 2,

gridRows : app.style.gridRows - 2,

}, style );

//下記で自身のgridCols,gridRowsを参照しているので、2つに分けて上記であらかじめ定義

style = Object.assign( {

gridCol : 1,

gridRow : 1,

innerCols : style.gridCols / app.style.gridsPerInnerCell,

innerRows : style.gridRows / app.style.gridsPerInnerCell,

innerCellSize : app.style.innerCellSize,


showCursorOnlyWaitingKey : true, //文字列の末尾に来た時だけカーソルを表示する

cursorBlink : 500, //カーソルのブリンク間隔(0ならブリンクしない)

}, style );


super( style, app );


this.clear();


//現在値

this.showCursor = this.style.showCursorOnlyWaitingKey == false; //カーソル描画

this.cursorBlink_beforeTime = 0; //カーソルブリンク制御 経過時間

this.cursorBlink_toggle = true; //カーソルブリンク制御 描画トグル

}

clear() {//m

//文字表示マスの初期化

this.inner = new Array();

for( let row = 0; row < this.style.innerRows; row ++ ) {

this.inner.push( new Array() );

}

this.innerCursorCol = 0; //カーソル位置 横

this.innerCursorRow = 0; //カーソル位置 縦

}

linebreak() {//m

this.innerCursorCol = 0;


//ウィンドウの途中行なら

if( this.innerCursorRow < this.style.innerRows - 1 ) {

this.innerCursorRow ++;

} else {

//ウィンドウの最下行なら

//1行スクロール

this.inner.shift();

this.inner.push( new Array() );

console.log( "scroll" );

}

}

async write( messageString, waitTime ) {//m

//check. ウェイト未指定ならデフォルト値使用

if( waitTime == undefined ) {

this.waitTime = 100;

} else {

this.waitTime = waitTime;

}

//check. 文字列の末尾に来た時だけカーソルを表示する場合

if( this.style.showCursorOnlyWaitingKey ) {

this.showCursor = false;

}

//check. カーソルのブリンクに関する初期化

if( this.style.cursorBlink ) {

this.cursorBlink_beforeTime = Date.now();

this.cursorBlink_toggle = false;

}


//1文字ずつ表示

for( let i = 0; i < messageString.length; i++ ) {

let ch = messageString.charAt( i );

//check. エスケープシーケンス

if( ch == "#" ) {

let ch2 = messageString.charAt( i + 1 );

if( ch2 == "n" ) {

this.linebreak();

i++;

} else if( ch2 == "k" ) {

let bak = this.showCursor;


//check. 文字列の末尾に来た時だけカーソルを表示する場合

if( this.style.showCursorOnlyWaitingKey ) {

this.showCursor = true;

}

await this.hitanykey();

this.showCursor = bak;

i++;

} else if( ch2 == "w" ) {

let res = messageString.substr( i ).match( /#w(\d+)/ );

if( res ) {

this.waitTime = Number( res[ 1 ] );

i += res[ 0 ].length - 1;

}

} else {

}

continue;

}

this.inner[ this.innerCursorRow ][ this.innerCursorCol ] = ch;

this.innerCursorCol ++;

//check. 右端なら改行

if( this.innerCursorCol == this.style.innerCols ) {

this.linebreak();

}


//1文字あたりの時間待ち

if( this.waitTime > 0 ) await this.app.delay( this.waitTime );

}


}

hitanykey() {

let bak = onkeydown;

return new Promise( function( promiseOk ) {

onkeydown = promiseOk;

} ).then( function() {

onkeydown = bak;

} );

}

typeKey( key ) {

this.waitTime = 0;

}

draw( cc ) {//m

super.draw( cc );


//check. カーソルをブリンクさせる場合は

if( this.style.cursorBlink ) {

let diff = Date.now() - this.cursorBlink_beforeTime;

//check. ブリンク間隔を越えたら

if( diff >= this.style.cursorBlink ) {

this.cursorBlink_toggle = ! this.cursorBlink_toggle;

this.cursorBlink_beforeTime = Date.now();

}

}


//マトリクスを描画

cc.fillStyle = "black";

cc.font = this.style.innerCellSize + "px''";

for( let row = 0; row < this.style.innerRows; row++ ) {

let gy = row * this.style.innerCellSize;

for( let col = 0; col < this.inner[ row ].length; col++ ) {

let gx = col * this.style.innerCellSize;

cc.fillText( this.inner[ row ][ col ], gx, gy + this.style.innerCellSize );


}

}


//カーソル表示

if( this.showCursor && this.cursorBlink_toggle ) {

let gx = this.innerCursorCol * this.style.innerCellSize;

let gy = this.innerCursorRow * this.style.innerCellSize;

cc.fillText( "▼", gx, gy + this.style.innerCellSize );

}

}

}//MessageWindow



// |

// V

//---■■■ 5 List ■■■

class List extends Craft {//c

constructor( src, style, app ) {

//check. styleのデフォルト値と上書き

style = Object.assign( {

innerCellSize : app.style.gridCellSize,

gridCol : 0,

gridRow : 0,

}, style );


//リストの項目

let items;

if( src.items instanceof Function )

items = src.items.call( app ); //関数から得る

else

items = src.items;


//check. リストの列数

if( style.gridCols == undefined ) {

//各列において、その全行の文字列の中での最大文字数を得る

//その最大文字数をstyle.gridColsへ加算していき、ウィンドウの横幅とする

style.gridCols = 0;

for( let column of src.columns ) {

let strings = new Array();

for( let item of items ) {

let string = ( item[ column.key ] instanceof Function )

? item[ column.key ].call( app )

: item[ column.key ];

strings.push( string );

}

column.cols = Utl.maxLengthBinDiv2( strings ) + 1; //その列の幅

column.col = style.gridCols; //その列の位置

style.gridCols += column.cols;

}

//check. タイトルのほうが長ければ

if( src.title != undefined ) {

style.gridCols = Math.max( Utl.lengthBinDiv2( src.title ), style.gridCols );

}

//check.

if( style.gridPaddingLeft != undefined ) style.gridCols += style.gridPaddingLeft;

}

//check. リストの行数

if( style.gridRows == undefined ) {

style.gridRows = items.length;

//check.

if( src.title ) style.gridRows++;

}


super( style, app );


this.src = src;

this.items = items;

}

draw( cc ) {//m


this.drawWindow( cc );


//debug. テキストエリアに水色枠

if( 0 ) {

cc.strokeStyle = "cyan";

cc.strokeRect( 0, 0, this.w, this.h );

}


cc.fillStyle = "black";

cc.font = this.style.innerCellSize + "px ''";


//タイトル

if( this.src.title ) {

let gx = ( this.style.gridPaddingLeft - 0.3 ) * this.app.style.gridCellSize;

let gy = this.style.innerCellSize * 0.65;

cc.fillText( this.src.title, gx, gy );

}


//リスト

cc.save();

this.translateToItemArea( cc );


//行

for( let i = 0; i < this.items.length; i++ ) {

let item = this.items[ i ];

let gy = this.style.innerCellSize * i;

//列

for( let col = 0; col < this.src.columns.length; col++ ) {

let column = this.src.columns[ col ];

//値が関数になっている場合に対応 たとえば、shopの「所持金」表示

let string = ( item[ column.key ] instanceof Function )

? item[ column.key ].call( this.app )

: item[ column.key ];

let gx = column.col * this.style.innerCellSize;

//check. 右寄せ指定 たとえば数値表示

if( Utl.accessBy( column, "align" ) == "right" ) {

gx += column.cols * this.style.innerCellSize;

gx -= cc.measureText( string ).width;

}


cc.fillText( string, gx, gy + this.style.innerCellSize );

//debug. 列に緑枠

if( 0 ) {

cc.strokeStyle = "green";

cc.strokeRect(

column.col * this.style.innerCellSize,

gy,

column.cols * this.style.innerCellSize,

this.style.innerCellSize

);

}

}//for

}//for

cc.restore();

}//draw()

translateToItemArea( cc ) {//m

if( this.src.title ) cc.translate( 0, this.style.innerCellSize );

cc.translate( this.style.gridPaddingLeft * this.style.innerCellSize, 0 );

}

}//List

// |

// V

//---◆◆◆ 6 Menu ◆◆◆

class Menu extends List {//c

constructor( mode, context, src, style, app ) {

//check. 表の列設定が未定義なら1列でnameを並べるだけの通常メニュー

if( src.columns == undefined ) {

src.columns = [ { key : "name" } ];

}

//check. 左端にカーソル用の空白エリア

if( style.gridPaddingLeft == undefined ) style.gridPaddingLeft = 1;


super( src, style, app );


this.mode = mode;

this.context = context;

this.src = src;

//check. contextをthisで参照できるように

if( this.src.context ) this.context = this.src.context;


//カーソルが横にも動く場合のため(未対応中)

this.itemCols = 1; //現在使用していない。

this.itemRows = this.items.length; //縦方向は基本なのでこれは使用している


this.cursorX = 0;

this.cursorY = 0;

}

select() {//m

return new Promise(

function( promiseOk ) {

console.log( "promised 'select'" );

this.promiseOk = promiseOk;

}.bind( this )

).then(

function( result ) {

console.log( "promise 'select' completed" )

return result;

}

);

}

typeKey( key ) {//m

let addX = ( key == 39 ) - ( key == 37 );

let addY = ( key == 40 ) - ( key == 38 );


if( addX || addY ) {

this.cursorX += addX;

this.cursorY += addY;

//check.

if( this.cursorY < 0 ) this.cursorY = 0;

if( this.cursorY >= this.itemRows ) this.cursorY = this.itemRows - 1;


//カーソル 移動のタイミングで「点滅の点灯開始状態」にする

//こうしないとチラチラして見づらい

this.app.metronomez.forCursor.toggle = true;

this.app.metronomez.forCursor.time = 0;

} else {


switch( key ) {

case 32://SPACE

this.app.artz.soundz.pushA.rapidPlay();


this.selectedItem = this.items[ this.cursorY ];


//選択した項目の形態

let menusrc;

//選択した項目はメニューであるA

if( this.selectedItem.items ) {

menusrc = this.selectedItem;

} else if( this.src.assistMenu ) {

//選択した項目はメニューではないが、

//this.srcにassistMenu指定がある。B

menusrc = Utl.copyObjectKai( this.src.assistMenu );

menusrc.items = this.selectedItem[ this.src.name ];

} else if( this.selectedItem[ "assistMenu" + this.context ] ) {

//選択した項目はメニューではないが、

//this.selectedItem内にcontextMenu指定がある。C

menusrc = this.selectedItem[ "assistMenu" + this.context ];

} else {

//メニューの終了

//ストーリー内でメニューの結果を待っている場合

if( this.mode == "selectmode" ) {

//選択結果を返す

this.promiseOk( this.getResult() );

} else {

//campなどでオペレーションする場合

//埋め込まれたスクリプトをもとに実行する

this.execute();

}

return;

}


//次のメニューを表示

let col = menusrc.col == undefined

? this.selectedItem.name.length + 2

: menusrc.col;

let row = menusrc.row == undefined

? this.cursorY + 2

: menusrc.row;

let menu = new Menu( this.mode, this.context, menusrc, {

gridCol : col,

gridRow : row,

}, this.app );

this.appendChild( menu );

menu.promiseOk = this.promiseOk;


break;

case 66://B

//セレクトモードでトップメニューがキャンセルされた

let thisIsTop = ! ( this.parent instanceof Menu );

if( this.mode == "selectmode" && thisIsTop ) {

this.promiseOk( null );

} else {

//それ以外

this.parent.removeChild( this );

}

break;

}//switch

}//if else

}//typeKey()

// |

// V

getResult() {//m

let result = new Object();

result.menus = new Array();

//自分自身から上へさかのぼってmenusへ加えていく

let element = this;

while( element instanceof Menu ) {

result.menus.unshift( element );

element = element.parent;

}

result.argz = new Object();

for( let menu of result.menus ) {

if( menu.src.story ) result.story = menu.src.story; //埋め込みスクリプト

result.argz[ menu.src.argzName ] = menu.selectedItem; //そのための引数

}

result.selectedItem = result.menus[ result.menus.length - 1 ].selectedItem;


/*

メニューの選択結果は、

result.menus たどったメニュー

result.argz たどって得られた引数リスト

result.story たどりの途中で得られた埋め込みスクリプト

result.selectedItem 最後に選択した項目

なお、storyとargzは

story( argz )

のように実行する。

argzはstoryの中で、argz.char1などのように参照する。

storyはこのファイルの最初のほうのメニュー定義部分などで定義されているもの。

argz.char1のchar1などは、同じくメニュー定義部分で、argzKey : "char1"

のように定義されている。

storyの内容も、storyの中でどんな引数を使うかもユーザーが定義する。

*/

return result;

}

async execute() {//m

let result = this.getResult();


//check.

if( ! result.story ) {

alert( "storyが未定義の状態です。at menu.execute()" );

}


await result.story.call( this.app, result.argz );


//check. storyによってサブメニューの元となったモノがなくなった。

/*

たとえば、

コマンド?:どうぐ

だれの?:キャラ1

どれ?:なんこう

どうする?:つかう

だれに?:キャラ1

として、なんこうを使ったら、なんこうはなくなるので、

なんこうに由来する どうする? だれに? の2つのメニューは

消さなくてはならない。その処理↓

*/

for( let i = result.menus.length - 1; i > 0; i -- ) {

let menu = result.menus[ i ];

let selfItem = menu.parent.selectedItem;

//親メニューに自身の選択項目がない。

if( ! menu.parent.items.includes( selfItem ) ) {

menu.parent.removeChild( menu );

//メニューを1つ消せば以降のサブメニューはchildなので一緒に消える

}

}

}

draw( cc ) {//m

super.draw( cc ); //extends List


cc.save();


this.translateToItemArea( cc );


//カーソルを描画

if( this.app.currentCraft != this ||

this.app.metronomez.forCursor.toggle ) {

let w = this.style.innerCellSize * .7;

let h = this.style.innerCellSize * .8;

let gx = - this.style.innerCellSize + ( this.style.innerCellSize - w ) / 2;

let gy = this.cursorY * this.style.innerCellSize + ( this.style.innerCellSize - h ) / 2 + 1;

cc.beginPath();

cc.moveTo( gx, gy );

cc.lineTo( gx + w, gy + h / 2 );

cc.lineTo( gx, gy + h );

cc.closePath();

cc.fillStyle = "black";

cc.fill();

}


cc.restore();


super.drawChildren( cc );

}//draw()

}//Menu

//---■■■ 7 Title ■■■

class TitleScreen extends Craft {//c

constructor( style, app ) {

super( style, app );

}

async typeKey( key ) {//m

this.app.artz.soundz.pushA.play();



//TitleScreenを削除

this.app.removeChild( this );


//MapScreenを作成

this.app.mapScreen = new MapScreen( {

innerCellSize : this.app.style.gridCellSize * 2,

gridCol : 0,

gridRow : 0,

gridCols : this.app.style.gridCols + ( this.app.style.gridCols % 2 ? 0 : 1 ),

gridRows : this.app.style.gridRows + ( this.app.style.gridRows % 2 ? 0 : 1 ),

//偶数なら奇数にする

}, this.app );

this.app.appendChild( this.app.mapScreen );


//スタート時ストーリー

let map = this.app.memoryCard.player.position.map;

let x = this.app.memoryCard.player.position.x;

let y = this.app.memoryCard.player.position.y;

switch( 2 ) {

case 0: this.app.mapScreen.playerMapMoveTo( map, x, y ); //memorycard

break;

case 1: this.app.mapScreen.playerMapMoveTo( map, 3, 8 ); //外

break;

case 2: this.app.mapScreen.playerMapMoveTo( App.townmap, 3, 5 );//町

break;

}


//雨を降らせる

this.app.effectz.startRain( this.app.mapScreen, 10, 1, 3 );



this.app.openMessage();

await this.app.message.write( "スタート#k" );

this.app.closeMessage();

}

draw( cc ) {//m

cc.fillStyle = "black";

cc.fillRect( 0, 0, this.w, this.h );

cc.font = this.style.innerCellSize + "px''";

cc.fillStyle = "white";

cc.fillText( "hit any key", this.w * .35, this.h * .75 );

}

}//TitleScreen

// |

// V

//---◆◆◆ 8 Map ◆◆◆

class MapScreen extends Craft {//c

constructor( style, app ) {

// style = Object.assign( {

// gridCols : app.style.gridCols,

// gridRows : app.style.gridRows,

// }, style );

style = Object.assign( {

innerCols : style.gridCols / app.style.gridsPerInnerCell,

innerRows : style.gridRows / app.style.gridsPerInnerCell,

}, style );


super( style, app );


this.actions = new Array();


this.scroll = {

count : 0,

isAnimating : false,

tweakGx : 0,

tweakGy : 0,

}

}

assignStyle( newStyle ) {

super.assignStyle( newStyle );


this.style.innerColsHalf = Math.floor( this.style.innerCols / 2 );

this.style.innerRowsHalf = Math.floor( this.style.innerRows / 2 );

}

// |

// V

tweakColForLooping( targetCol ) {//m

//tweak. ループ時は範囲外の値を範囲内に調整する

if( this.cfg.maploop ) {

if( targetCol < 0 )

targetCol += this.mapCols;

else if( targetCol >= this.mapCols )

targetCol -= this.mapCols;

}

return targetCol;

}

tweakRowForLooping( targetRow ) {//m

//tweak. ループ時は範囲外の値を範囲内に調整する

if( this.cfg.maploop ) {

if( targetRow < 0 )

targetRow += this.mapRows;

else if( targetRow >= this.mapRows )

targetRow -= this.mapRows;

}

return targetRow;

}

getPositionAwayBy( step ) {//m

let dir = this.app.memoryCard.player.direction;

let x = this.playerCol + ( ( dir == 2 ) - ( dir == 0 ) ) * step;

let y = this.playerRow + ( ( dir == 3 ) - ( dir == 1 ) ) * step;

return { x : x, y : y }

}

// |

// V

async typeKey( key ) {//m


//check. キーを受け付けないとき

if( this.app.keyLocked || this.scroll.isAnimating ) return;


if( key == 32 ) {

//check. 向いている方向に何かがある

let pos = this.getPositionAwayBy( 1 );

let bit;

if( bit = Utl.accessBy( this.mapBits, pos.y, pos.x ) ) {

if( bit == "□" ) {

pos = this.getPositionAwayBy( 2 );

this.actions.push( { x : pos.x, y : pos.y, type : "contact" } );

return;

}

}

if( bit = Utl.accessBy( this.storyBits, pos.y, pos.x ) ) {

if( bit.sprite ) {

this.actions.push( { x : pos.x, y : pos.y, type : "contact" } );

return;

}

}


//コマンドメニューを開く

this.app.artz.soundz.pushA.rapidPlay();

let campMenu = new Menu( "executemode", "Camp", this.app.menusrcz.camp, {

gridCol : 1,

gridRow : 1,

}, this.app );

this.app.appendChild( campMenu );

campMenu.focus();

}

}

// |

// V

senseKey( key ) {//m


//check. キーを受け付けないとき

if( this.app.keyLocked || this.scroll.isAnimating ) return;


let addX, addY;


switch( key ) {

case 37: this.app.memoryCard.player.direction = 0; addX = -1; addY = 0; break;

case 38: this.app.memoryCard.player.direction = 1; addX = 0; addY = -1; break;

case 39: this.app.memoryCard.player.direction = 2; addX = 1; addY = 0; break;

case 40: this.app.memoryCard.player.direction = 3; addX = 0; addY = 1; break;

default: return;

}


//check. 移動先について確認

let destinCol = this.tweakColForLooping( this.playerCol + addX );

let destinRow = this.tweakRowForLooping( this.playerRow + addY );

//移動先は壁

let bit = Utl.accessBy( this.mapBits, destinRow, destinCol );

if( bit != null && this.cfg.walls.indexOf( bit ) > -1 ) {

this.app.artz.soundz.pushWall.play();

return;

}

//移動先はスプライト

let sprite = Utl.accessBy( this.storyBits, destinRow, destinCol, "sprite" );

if( sprite ) return;


//1マススクロールの起動

this.scroll.addX = addX;

this.scroll.addY = addY;

this.scroll.count = 0;

this.scroll.isAnimating = true;


}//senseKey

// |

// V

frameForScroll() {//m

//1マススクロールにおける、1ドット分のスクロール処理


this.scroll.tweakGx -= this.scroll.addX;

this.scroll.tweakGy -= this.scroll.addY;

//tweakGx,Gyが1ドットスクロールの主要のしくみ

//それについてはthis.draw()を参照


this.scroll.count ++;

//check. スクロールの終了

if( this.scroll.count == this.style.innerCellSize ) {

this.scroll.isAnimating = false;


this.scroll.tweakGx = 0;

this.scroll.tweakGy = 0;

//draw()はスクロール終了後も参照するからここで0にする。


this.playerCol = this.tweakColForLooping( this.playerCol + this.scroll.addX );

this.playerRow = this.tweakRowForLooping( this.playerRow + this.scroll.addY );


//マップ外に乗ったストーリー発生

if( ! this.cfg.maploop && (

this.playerCol < 0 || this.playerCol >= this.mapCols ||

this.playerRow < 0 || this.playerRow >= this.mapRows ) ) {

this.actions.push( { type : "exit" } );

} else {

//座標に乗ったストーリー発生

this.actions.push( { x : this.playerCol, y : this.playerRow, type : "walk" } );

}

}//if スクロール終了

}//frameForScroll()

// |

// V

playerMapMoveTo( mapdatafunc /*other args*/ ) {//m


//マップ間移動


//check. 

if( this.app.bgm ) {

this.app.bgm.pause();

this.app.bgm.currentTime = 0;

}


//データを記述した関数からデータを取り出す1

let commentData = mapdatafunc.toString().match( /\/\*\r\n([\s\S]+)\r\n\*\// )[ 1 ];


//データを記述した関数からデータを取り出す2

this.cfg = mapdatafunc();



//取り出したデータを使えるように加工

this.mapBits = new Array();

this.storyBits = new Array(); //位置に設定されたストーリー


//マップデータを読み取る ここから

let shortcutPositionz = new Object();

let lines = commentData.split( /\r\n/ );


for( let y = 0; y < lines.length; y++ ) {

let line = lines[ y ];


this.mapBits[ y ] = new Array();

for( let x = 0; x < line.length; x++ ) {

let mapBit = line.substr( x, 1 );


//check. 半角文字のときはストーリーへのショートカット

if( mapBit.match( /[0-9a-z ]/i ) ) {

let shortcutId = mapBit + line.substr( x + 1, 1 );

shortcutId = shortcutId.replace( / /, "" );


//check. ショートカットがリンク切れ

if( ! this.cfg.shortcuts[ shortcutId ] ) {

alert( "マップ上に配置したショートカットがリンク切れ id:" + shortcutId );

continue;

}


//マップ上のショートカットについては後で処理

shortcutPositionz[ shortcutId ] = {

x : x,

y : y,

};

//ショートカットをマップデータに置き換え

mapBit = this.cfg.shortcuts[ shortcutId ].bit;


x++; //1文字多く読んだから

}


this.mapBits[ y ].push( mapBit );

}

}//for


this.mapCols = this.mapBits[ 0 ].length;

this.mapRows = this.mapBits.length;


//storyBits初期化

for( let y = 0; y < this.mapRows; y++ ) {

this.storyBits[ y ] = new Array();

for( let x = 0; x < this.mapCols; x++ ) {

this.storyBits[ y ][ x ] = null;

}

}

//storyBitsにstoryを配置

for( let shortcutId in shortcutPositionz ) {

let pos =shortcutPositionz[ shortcutId ];

let shortcut = this.cfg.shortcuts[ shortcutId ];

this.storyBits[ pos.y ][ pos.x ] = shortcut;


//tweak. 座標情報をセット

shortcut.x = pos.x;

shortcut.y = pos.y;

}



//マップデータの端を超えるとマップの反対側になる計算

if( this.cfg.maploop ) {

this.patchwork = function( position, size ) { return ( position + size ) % size }

} else {

//町などループしない場合

this.patchwork = function( position ) { return position };

}



//マップデータを読み取る ここまで



//BGM

if( this.cfg.bgmTitle ) {

this.app.bgm = this.app.artz.bgms[ this.cfg.bgmTitle ];

this.app.bgm.loop = true;

this.app.bgm.play();

}


//メソッドの引数によって処理を分ける(オーバーロードみたいに)

let argtypes;

let argArray = Array.from( arguments );

argtypes = argArray.map( arg => ( typeof arg ).substr( 0, 1 ) ).join( "" );


//ショートカットを指定して移動

if( argtypes.indexOf( "fs" ) == 0 ) {


//playerMapMoveTo( function, string [,number] )

let shortcut = arguments[ 1 ];

this.playerCol = this.cfg.shortcuts[ shortcut ].x;

this.playerRow = this.cfg.shortcuts[ shortcut ].y;

//check.

if( typeof arguments[ 2 ] !== "undefined" )

this.app.memoryCard.player.direction = arguments[ 2 ];


} else if( argtypes.indexOf( "fnn" ) == 0 ) {


//移動先位置の座標指定して移動

//playerMapMoveTo( function, number, number [,number] )

this.playerCol = arguments[ 1 ];

this.playerRow = arguments[ 2 ];

//check.

if( typeof arguments[ 3 ] !== "undefined" )

this.app.memoryCard.player.direction = arguments[ 3 ];

} else {


alert( "playerMapMoveTo()の引数リストが想定外 : " + argtypes );


}


}//playerMapMoveTo()


searchStory() {//m

let hits = new Array();


/* 優先順位

処理順 1. 2. 3.

action walk>walk  >walk

x,y 全体>ある位置>全体

例 沼地>ジャンプ>エンカウント


つまり、一歩進んでwalkアクションになると、3種類のストーリーが起こりうる。

たとえば、沼地に入ったら沼地のダメージのストーリーがまず行われ、

つづいて、マップジャンプがそこに設定されていたらジャンプのストーリーが行われる。

最後にエンカウントのストーリーが入るが、ジャンプのストーリーは

打ち切りのフラグをreturnするので、エンカウントは行われない。

優先順位を考慮しないと、マップジャンプしたあとに突然戦闘になるとか、

沼地に踏み込んだのに、ダメージが減らないままマップジャンプするとか

になってしまう。

*/


for( let action of this.actions ) {

let story;


//1. マップチップのストーリー検索

let bit;

if( action.y != undefined && action.x != undefined )

bit = this.mapBits[ action.y ][ action.x ];

else

bit = this.cfg.outside;

if( this.cfg.mapBitStoriez[ bit ] )

if( story = this.cfg.mapBitStoriez[ bit ][ action.type ] ) {

hits.push( { action : action, story : story } );

}


//2. マップ上の位置に設定されたストーリー

if( story = Utl.accessBy( this.storyBits, action.y, action.x, "story", action.type ) ) {

hits.push( { action : action, story : story } );

}


//3. マップ自体に設定されたストーリー

if( story = this.cfg.story[ action.type ] ) {

hits.push( { action : action, story : story } );

}

}


this.actions.length = 0;

return hits;

}


draw( cc ) {//m

cc.fillStyle = this.style.backgroundColor;

cc.fillRect( 0, 0, this.w, this.h );


this.effectDraw1( cc );



let image;


cc.save();

cc.translate(

-this.style.innerCellSize / 2 + this.scroll.tweakGx,

-this.style.innerCellSize / 2 + this.scroll.tweakGy

);

//tweakX,Yが描画位置を1ドットずつずらすので

//きれいに1ドットスクロールしてるようにみえる


cc.font = this.style.innerCellSize + "px ''";

cc.fillStyle = "rgb(0,255,0)";


//this.playerCol,this.playerRowが画面中央に来るように描画開始座標を調整

let sx = this.playerCol - this.style.innerColsHalf;

let sy = this.playerRow - this.style.innerRowsHalf;


for( let inner_row = 0; inner_row < this.style.innerRows; inner_row++ ) {

let map_row = this.patchwork( sy + inner_row, this.mapRows );//両端ループ処理

let gy = inner_row * this.style.innerCellSize;

for( let inner_col = 0; inner_col < this.style.innerCols; inner_col++ ) {

let map_col = this.patchwork( sx + inner_col, this.mapCols );//両端ループ処理

let gx = inner_col * this.style.innerCellSize;


//マップを描画

let bit = Utl.accessBy( this.mapBits, map_row, map_col );

//check.

if( bit == undefined ) bit = this.cfg.outside;

image = this.cfg.images[ bit ] ? this.cfg.images[ bit ] : bit;

cc.fillText( image, gx, gy + this.style.innerCellSize );


//スプライトを描画

let story = Utl.accessBy( this.storyBits, map_row, map_col );

//check.

if( ! story ) continue;

if( ! story.sprite ) continue;

image = story.sprite;

cc.fillText( image, gx, gy + this.style.innerCellSize );

}

}



//debug. グリッド

if( 0 ) {

cc.strokeStyle = "rgba(255,255,255,.125)";

for( let row = 0; row < this.style.innerRows; row ++ ) {

for( let col = 0; col < this.style.innerCols; col ++ ) {

let x = col * this.style.innerCellSize;

let y = row * this.style.innerCellSize;

cc.strokeRect( x, y, this.style.innerCellSize, this.style.innerCellSize );

}

}

}


cc.restore();


//debug. 画面の十字線

if( 0 ) {

cc.strokeStyle = "red";

cc.beginPath();

cc.moveTo( 0, cc.canvasHeight / 2 );

cc.lineTo( cc.canvasWidth, cc.canvasHeight / 2 );

cc.stroke();

cc.beginPath();

cc.moveTo( cc.canvasWidth / 2, 0 );

cc.lineTo( cc.canvasWidth / 2, cc.canvasHeight );

cc.stroke();

}


//プレイヤーキャラ描画

cc.fillStyle = "cyan";

cc.font = this.style.innerCellSize + "px ''";

let gx = ( cc.canvasWidth - this.style.innerCellSize ) / 2;

let gy = ( cc.canvasHeight - this.style.innerCellSize ) / 2 + this.style.innerCellSize;

let sprite = this.app.artz.spritez.player;

let idx = this.app.memoryCard.player.direction * 2 + sprite.metronome.index;

image = sprite.images[ idx ];

cc.fillText( image, gx, gy );

//debug. 座標表示

if( 0 ) {

cc.fillStyle = "red";

cc.fillText( this.playerCol + "," + this.playerRow, gx, gy );

}


this.effectDraw2( cc );


this.drawChildren( cc );


}//draw()

}//MapScreen


//---■■■ 9 BattleView ■■■

class BattleView extends Craft {//c

constructor( monsters, style, app ) {

super( style, app );

this.monsters = monsters;

}

draw( cc ) {//m

this.effectDraw1( cc );

cc.drawImage( this.app.artz.imagez.battleBG, 0, 0, this.w, this.h );

if( 0 ) {

let step = this.w / ( this.monsters.length );

for( let i = 0; i < this.monsters.length; i++ ) {

let monster = this.monsters[ i ];

//check. 倒れたモンスター

if( monster.property.hp == 0 ) continue;

let zm = 0.1;

let mw = monster.image.width * zm;

let mh = monster.image.height * zm;

let gx = i * step + this.w / this.monsters.length / 2 - mw / 2;

let gy = this.h * 0.9 - mh;

cc.drawImage( monster.image, gx, gy, mw, mh );


}

} else {

let step = this.w / ( this.monsters.length + 1 );

for( let i = 0; i < this.monsters.length; i++ ) {

let monster = this.monsters[ i ];

//check. 倒れたモンスター

if( monster.property.hp == 0 ) continue;

let zm = 0.1;

let mw = monster.image.width * zm;

let mh = monster.image.height * zm;

let gx = ( i + 1 ) * step - mw / 2;

let gy = this.h * 0.9 - mh;

if( monster.flg ) {

cc.drawImage( monster.image, gx, gy, mw, mh );

} else {

cc.save();

//裏画面でシルエット画像を作成

let bcc = document.createElement( "canvas" ).getContext( "2d" );

bcc.canvas.width = monster.image.width;

bcc.canvas.height = monster.image.height;

bcc.drawImage( monster.image, 0, 0 );

bcc.globalCompositeOperation = "source-in";

bcc.fillStyle = "black";

bcc.fillRect( 0, 0, monster.image.width, monster.image.height );

//それを描画

cc.drawImage( bcc.canvas, gx, gy, mw, mh );

cc.restore();

}

}

}//if

this.effectDraw2( cc );

}//draw()

}//BattleView


//---■■■ 10 Effectz ■■■

class Effectz {//c

constructor( app ) {

this.app = app;

}


async zoom( target, metronomeMaxMs, fromScale, toScale, endTimeMs ) {

return new Promise( function( promiseOk ) {

let dir = fromScale < toScale ? 1 : -1;

let nowScale = fromScale;

let metronome = this.app.addMetronome( "forZoom", metronomeMaxMs );

target.beforeEffects.push( function( cc ) {

cc.scale( nowScale, nowScale );

//check.

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let remainingMotion = toScale - nowScale; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronomeMaxMs;//残り描画

let step = remainingMotion / remainingDrawing;


nowScale += step;

//check.

if( ( toScale - nowScale ) * dir <= 0 ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}


}.bind( this ) );

}.bind( this ) );

}

startRain( target, metronomeMaxMs, step, hPerW ) {//m

//hPerW 雨粒の傾き


let rainLen = 4; //雨粒の長さ

//雨粒の長さと傾きが作る三角形の

let W = rainLen / Math.sqrt( 1 + hPerW * hPerW ); //底辺

let H = W * hPerW; //高さ

//雨の傾きによる三角状の空欄をなくすための、target.w増分

let plusW = target.h / hPerW;


let rains = new Array();

for( let i = 0; i < 100; i++ ) {

rains[ i ] = {

x : Math.floor( Math.random() * target.w ),

y : Math.floor( Math.random() * target.h ),

}

}

let metronome = this.app.addMetronome( "forRain", metronomeMaxMs );

let drawFunc = function( cc ) {

cc.strokeStyle = "cyan";

cc.lineWidth = 0.5;

for( let rain of rains ) {

cc.beginPath();

cc.moveTo( rain.x, rain.y );

cc.lineTo( rain.x - W, rain.y + H );

cc.stroke();


//check.

if( ! metronome.changed ) continue;


//雨粒の位置を更新

rain.y += H * step;

//check. 画面外

if( rain.y > target.h ) rain.y = rain.y % target.h;

//rain.y = 0; としてしまうと、各雨粒の水平位置がそろってしまう。

rain.x -= W * step;

//check. 画面外

if( rain.x < 0 ) {

rain.x = Math.floor( Math.random() * target.w + plusW );

rain.y = rain.y % target.h;;

}

}

}.bind( this );

drawFunc.id = "rain";

target.afterEffects.push( drawFunc );


this.app.effectz.endRain = function() {

target.afterEffects.splice( target.afterEffects.indexOf( drawFunc ), 1 );

this.app.deleteMetronome( metronome );

}

}

//戦闘スタート時の演出

async battleStart( battleView ) {//m

this.app.stopFrame();


let cc = this.app.cc;

let step = 13;

let movl = 200

for( let i = 1; i <= step; i ++ ) {

let theta = Math.PI * 2 / step * i;

let alpha = 1 / step * i;

let movx = movl - movl / step * i;

this.app.draw( cc );

cc.save();

cc.globalAlpha = alpha;

cc.translate( battleView.x + movx, battleView.y );

cc.rotate( theta );


battleView.draw( cc );

cc.restore();


await this.app.delay( 60 );

}

this.app.startFrame();


}


async rotate( target, metronomeMaxMs, maxTheta, endTimeMs ) {//m

return new Promise( function( promiseOk ) {


let theta = 0;

let metronome = this.app.addMetronome( "forRotate", metronomeMaxMs );


target.beforeEffects.push( function( cc ) {

cc.rotate( theta );

//check.

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let remainingMotion = maxTheta - theta; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronomeMaxMs;//残り描画

let step = remainingMotion / remainingDrawing;

theta += step;

//check.

if( theta >= maxTheta ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}


}.bind( this ) );

}.bind( this ) );

}

async quake( target, width, speed, endTimeMs ) {//m

return new Promise( function( promiseOk ) {


let metronome = this.app.addMetronome( "forQuake", speed / 2 );

let draw = function( cc ) {

let x = metronome.toggle ? 0 : width;

cc.translate( x, 0 );

//check. 終了

if( metronome.changed && Date.now() >= endTimeMs ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}.bind( this );

target.beforeEffects.push( draw );


}.bind( this ) );

}

async flash( target, color, endTimeMs ) {//m


return new Promise( function( promiseOk ) {

let metronome = this.app.addMetronome( "forFlash", 0 );

target.afterEffects.push( function( cc ) {

if( metronome.toggle ) {

cc.fillStyle = color;

cc.fillRect( 0, 0, target.w, target.h );

}

//check. 予定時刻

if( Date.now() >= endTimeMs ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}.bind( this ) );

}.bind( this ) );

}

async fade( target, metronormMaxMs, inout, color, endTimeMs ) {//m

return new Promise( function( promiseOk ) {

let from, to;

if( inout == "out" ) {

from = 0;

to = 1;

} else {

from = 1;

to = 0;

}

let nowAlpha = from;

let metronome = this.app.addMetronome( "forFade", metronormMaxMs );


let drawFunc = function( cc ) {

//メトロノームにより値を進める。

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let step;

let remainingMotion = to - nowAlpha; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

//check. 超えた値を修正

if( remainingTimeMs < 0 ) remainingTimeMs = 0;

let remainingDrawing = remainingTimeMs / metronormMaxMs;//残り描画

step = remainingMotion / remainingDrawing;


nowAlpha += step;

//check. 超えた値を修正

if( nowAlpha > 1 ) nowAlpha = 1;

else if( nowAlpha < 0 ) nowAlpha = 0;

}


cc.globalAlpha = nowAlpha;

cc.fillStyle = color;

cc.fillRect( 0, 0, target.w, target.h );


//check. 終了値に到達

if( nowAlpha == to ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}


}.bind( this );

drawFunc.id = "fade-" + inout;

target.afterEffects.push( drawFunc );

}.bind( this ) );

}


//utl

stepByRemaining( value, maxValue, endTimeMs, metronome ) {//m

//残りの動作量を残りの描画回数で割る。それをstepとする。

//動くたびに毎回計算しなおす。(ズレを吸収する)


let remainingMotion = maxValue - value; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronome.maxMs; //残り描画

let step = remainingMotion / remainingDrawing;

return step;

}

}//Effectz


App.worldmap = function() {/*

●〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃¥¥¥▲〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山山山山〃~~~~~~¥¥〃〃〃〃〃〃〃〃〃¥¥山山山¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山山〃〃〃〃~~~〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥¥〃〃1 〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃〃〃山山〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃山山山〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃山山山山山〃〃〃〃〃〃〃〃¥¥山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃山山山〃〃〃〃〃〃〃〃¥〃〃¥山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥¥〃〃〃〃山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥〃〃〃〃〃山山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山山山〃〃〃~~~〃〃〃〃〃〃〃〃〃〃山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山山〃〃~~~~¥¥〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山〃〃~~~¥¥¥〃〃〃〃〃〃〃〃山山山山山山山〃〃〃〃〃山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃¥¥¥¥〃〃〃〃〃〃〃〃〃山山山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃〃山山山山山山山山山山~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃山山山山山~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃〃〃〃〃~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃¥¥¥¥¥¥¥〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃¥¥¥¥¥¥¥¥〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃¥¥¥¥¥¥〃〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃¥¥¥¥¥〃〃〃〃〃〃〃〃〃〃~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃¥¥〃〃〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

*/

return {

maploop : true,

bgmTitle : "field",

walls : "~",

shortcuts : {

"1" : {

bit : "凸",

story : {

"walk" : async function( act ) {

this.keyLocked = true;

let nowMs = Date.now();

await this.effectz.fade( this.mapScreen, 50, "out", "black", nowMs + 500 );

this.mapScreen.playerMapMoveTo( App.townmap, "s", 2 );

await this.effectz.fade( this.mapScreen, 50, "in", "black", nowMs + 1000 );

this.keyLocked = false;

return true; //他のstory実行を打ち切る意

},

},

},

},

story : {

"walk" : function( act ) {

//エンカウント

if( Math.random() > .8 ) {

this.encounter();

}

},

},

mapBitStoriez : {

"沼" : {

"walk" : async function() {

this.keyLocked = true;

this.artz.soundz.damage.rapidPlay(); //音声

await this.effectz.flash( this.mapScreen, "red", Date.now() + 100 );

await this.delay( 200 );

this.keyLocked = false;

},

},

},

images : {

"¥" : "🌲",

"凸" : "🏰",

"山" : "⛰",

"~" : "🌊",

"沼" : "☠",

},

}//return

}//worldmap



App.townmap = function() {/*

木木木木木木木〃・・〃木木木木木木木木木〃・・〃〃〃〃〃〃〃

木■■■■■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■刀刀刀■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■・ws・■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■■□■■木〃・・〃木■■■扉■■■木〃・・〃〃〃〃〃〃〃

木木〃・剣木木〃・・〃〃花花花・花花花a2〃・・〃〃〃〃〃〃〃

s ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

木木盾・〃木木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■■□■■木〃・・〃a1〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■・ss・■木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■盾盾盾■木〃・・〃〃◇〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■■■■■木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木木木木木木木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃沼沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃沼沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃沼沼沼・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

*/

return {

maploop : false,

bgmTitle : "town",

shortcuts : {

"s" : {

bit : "・",

},

"ws" : {

bit : "👨‍🔧",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ここは刀剣屋だ。#k" );

this.closeMessage();

await this.shop( {

name : "shop",

title : "刀剣屋 'ロバートウッドテイル'",//全13, 半3

items : [

{ name : "どうけん", price : 100, },

{ name : "せいどうけん", price : 200, },

{ name : "てっけん", price : 300, },

{ name : "こうてつけん", price : 400, },

{ name : "いばらきけん", price : 18000, },

{ name : "ちばけん", price : 78000, },

{ name : "とっとりけん", price : 10102, },

{ name : "ふくしまけん", price : 29400, },

],

columns : [

{

key : "name",

},

{

key : "price",

align : "right",

}

],

}, {

"何を買うんだ?" : "何を買うんだ?",

"だな。" : "だな。",

"だ。買うかね?" : "だ。買うかね?",

"だ。買うかね?" : "だ。買うかね?",

"まいどあり!" : "まいどあり!",

"ありがとうございました!" : "ありがとうございました!",

} );

console.log( "shop closed." );

},

},

},

"ss" : {

bit : "👩‍🔧",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ここは道具屋よ。#n#k" );

this.closeMessage();

await this.shop( {

name : "shop",

title : "道具屋 'セリーヌ・デ・イオン'",//全13, 半3

items : [

{ name : "なんこう", price : 100, },

{ name : "羊皮紙", price : 200, },

{ name : "黄金の羊", price : 200000, },

],

columns : [

{

key : "name",

},

{

key : "price",

align : "right",

}

],

}, {

"何を買うんだ?" : "何を買いますか?",

"だな。" : "ね。",

"だ。買うかね?" : "です。買いますか?",

"まいどあり!" : "ありがとうございます!",

"ありがとうございました!" : "ありがとうございました!",

} );

console.log( "shop closed." );

},

},

},

"a1" : {

bit : "〃",

sprite : "🕵️‍♂️",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ドラゴンなら北のほうへ飛んで行ったぜ#k" );

this.closeMessage();

},

},

},

"a2" : {

bit : "〃",

sprite : "👩‍🚒",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "あなたのつるぎの使い方、\nふつうじゃないよね?#k" );

this.closeMessage();

},

},

},

},

story : {

"exit" : async function( e ) {

this.keyLocked = true;

this.artz.soundz.damage.rapidPlay(); //音声

let nowMs = Date.now();

await this.effectz.fade( this.mapScreen, 50, "out", "black", nowMs + 500 );

this.mapScreen.playerMapMoveTo( App.worldmap, "1", 3 );

await this.effectz.fade( this.mapScreen, 50, "in", "black", nowMs + 1000 );

this.keyLocked = false;

},

},


outside : "〃",

walls : "■□扉",

mapBitStoriez : {

"◇" : {

"walk" : function() {

this.effectz.quake( this.mapScreen, 3, 100, Date.now() + 1000 );

},

},

"沼" : {

"walk" : async function() {

this.keyLocked = true;

await this.effectz.flash( this.mapScreen, "red", Date.now() + 100 );

await this.delay( 100 );

this.keyLocked = false;

},

"exit" : function() {

this.effect_flash( "darkred" );

},

},

},

images : {

"木" : "🌳",

"花" : "🌷",

"扉" : "🚪",

"箱" : "📦",

"座" : "🪑",

"寝" : "🛏",

"∪" : "🏺",

"剣" : "⚔",

"刀" : "🗡",

"盾" : "🛡",

"沼" : "☠",

},

}//return

}//townmap




(未完成です)

この機能の良いところは、


正規表現ってなんだ?

「正規表現」とは、

たとえば、「*.txt」 と書けば、perikan.txt、panama.txt、hyper.txt、など、.txt という拡張子のあらゆるファイルを該当させることができ、これを「ワイルドカード」と言いますが、正規表現は「ワイルドカード」の高機能版みたいなものです。

*.txt はまだシンプルですが、慣れてくると、

/(\.)(match|split|matchAll|replace)(\(\s*)(\/)(.*?)(?<!\\\\)(\/[a-z]*)/

こんなふうに文字化けみたいになることが多いです。これは JavaScript の、「string.match( /abc/i )」というような文にマッチさせようとしている正規表現です。

長文の中から一部を取り出して、変更して元の場所に戻す、というようなことをやりたいときに力を発揮します。

ちょっと難しいですが、慣れるととても便利です。


"正規表現" という言葉の意味は、

「形式言語理論」という学問の中で導入された、「言語に対するフォーマット」みたいなもの?です。私もよく知らないので…

日本語など 同じ事がらでも話す人によっていろいろな表現がある「自然言語」に対して、プログラミング言語など表現の違いをあまり許さない言語を「形式言語」と言うそうです。

ある型式言語を「正規表現」で表せるならば、その型式言語は「正規言語」である、のだそうな。でいいのかな?

そういう難しい話なので、プログラミング言語の中で使う正規表現、たとえば、test.match( /*\.txt/ ) を見て、どこが「正規」で、どこが「表現」なのかと考えないほうが良いでしょう。

/*\.txt/、これがとにかく「正規表現」なんだ、と漠然と理解するのが良いです。


2021/11/2(火)

PC-9801 UV11 購入に踏み切った - その7

/* PC-9801 UV11 購入に踏み切った 各記事 一覧 */
その1(2021/9/18 土)
買いました。(品物未着)
購入品の内訳を紹介。

その2(2021/9/18 土)
届きました。
状態の確認。

その3(2021/9/19 日)
私の記念品としての確認。
その4(2021/9/24 金)
BIOS 取得作業
実機動作
→ 問題3つ発生。
その5(2021/9/25 土)
問題3
"ディスク書込禁止" 解決
そして、BIOS 取得

その6(2021/10/16 土)
ソフトウェア2点、ファイラー「FL」とテキストエディタ「JED」導入
その7(2021/11/2 火)
98実機でのアセンブラ
「98ハードに強くなる本II」のサンプルプログラム
その8(2021/11/21 日)
3モード FDD 壊れて再購入
N88-BASIC を購入
新品フロッピー10枚廃棄



今年の 9 月に私の趣味として、レトロ PC の PC-9801UV11 をオークションで競り落としました(と言っても入札したのは私だけでしたが)。

なんせ、30年前の機械なので、そのままではまともに動作せず、いろいろ修理をしました。

修理の甲斐あってハード的な部分はだいぶ落ち着いて、今ではソフトウェアに こり始めています。


購入の目的の1つに『この前購入した「98ハードに強くなる本II」のサンプルプログラムを試したい』というのがあり、今回それを実行にうつしました。

「98ハードに強くなる本II」(1988年 技術評論社)というのはこの本です。


今回のお話の結果として、私は感極まり、急いで このようなメモを書きました。

その場、その時でしか出てこない言葉があるので、忘れないうちにメモしたんです。

PC-9801 というのはやっぱり、Windows や Android のプログラミングとは比較にならないくらい面白い!

今までなんで忘れていたんだろうな。


「98ハードに強くなる本II」のサンプルプログラムは、

▼BASIC であったり、
▼アセンブラであったりと、


2種類のプログラミング言語で掲載されています。(両方同時掲載ではなく、話によってどちらか一方が掲載されている)

私は現状、BASIC環境(N88-日本語BASIC)はなかなか手に入らない状態なので、とりあえずアセンブラのほうを試すことにしました。


同書 P44 の リスト 1-1 は「キーボード入力されたキー内容をCRTに表示するプログラム」です。

(アセンブラのプログラミングの仕方は全然知らないので、LASM に同梱されていた Hello World! のアセンブラプログラム(HELLO.ASM)の中に、同書の リスト 1-1 を書きこみました)

これを LASM というアセンブラを使ってアセンブルしますが、その前に、LASM は「PC-9801 用」と書かれていないのに、どうして PC-9801 で実行できるのか、疑問ではないですか?


私はその辺が疑問でしたが、半信半疑で LASM 試用版をダウンロードして、PC-9801 上で実行してみたら、普通に動きました。

私も勉強になったんですが、PC-9801 はインテルの 8086 プロセッサの上位互換の CPUである V30 や 286 を搭載しているので、

「8086/8087 から Pentium までに対応した MS-DOS 汎用のマクロアセンブラです」

と紹介されている LASM は、普通に動作するんですね。


ここで、"Windows が動作するパソコン" も、インテルの CPU で動いているんだから 8086 の上位互換なのであって、LASM は動作するんじゃないか?と思いましたが、動きませんでした。

▼LASM.EXE を Windows10 で実行したところ

LASM.EXE は 「16bitアプリケーション」なので 64bitの Windows 上では動かないようです。

おなじインテルの CPU でも、ビット(アーキテクチャー)が異なれば、動かないということなんですね。


でも、LASM に同梱されている LASM32.EXE を代わりに使うと、Windows10 でも動作します。

(64bit版 Windows の「wow64」というしくみにより、32bitアプリケーションである LASM32.EXE が 64bit 上でも動作する)

ただし、それでアセンブルして(そしてリンカでリンクして)出来あがった実行ファイルは、結局「16bitアプリケーション」ということで、動作しませんでした。

▼LASM32.EXE を使えば、Windows 10上でも動作する(アセンブルできる)が、出来上がった実行ファイルは動作しない。

という区別があります。ある bit 用に作られた実行ファイルは異なる bit の OS 上では基本的には動作できません。

私もこのへんはあやふやでしたが、今回ちょっと調べて理解を深めました。


ちなみに、64bit Windows 上で「16bitアプリケーション」を動かすソフトウェアを探したらありました。

▼64bit Windows で、そのソフトを使って「16bitアプリケーション」を動かしたところ。

  1. https://github.com/otya128/winevdm/releases にアクセスする。
  2. ページの一番上の、「大きな文字のバージョン番号」をクリック。
  3. ページの下のほうの Assets の otvdm-v**.zip をクリック (**はそのバージョン番号)
    すると、zip がダウンロードされる。
  4. zip を展開する。
  5. 「16ビットだから、」と言われて実行できない .exe ファイルを、展開してできた otvdmw.exe へドラッグ&ドロップする。
    すると、上図のように「16bitアプリケーション」が実行される。
    ※完ぺきではないらしく、ものによっては動かないそうです。


話は戻って、LASM を PC-9801 上に持って行って、アセンブルを実行しました。


アセンブルすると、.obj ファイルができます。つづいてリンカを使って実行ファイルを作成します。↓


出来上がった実行ファイルを実行して、何かキーを押すと、こうなりました。 ピー!っと鳴って、

プログラムのキーの入力待ちの部分までは正常で、おそらく 20 行目の、

  INT 0C4H        ;N88-SYSTEM CALL  

…がいけないんじゃないかな?

よくはわかりませんが、環境は「N88-日本語BASIC」ではなく、「MS-DOS」なのでこのプログラムを実行すると、

”N88-BASIC のそのサブルーチンはどこにも無いのでダメです”

…ということで、「Int trap halt」("割り込み処理は行きづまって停止しました")となるんじゃないだろうか……。

BASIC が手元になく、よくわからないので、いまいちデバッグの手が出ません。


いったんあきらめて、なんか他にないかなと思って同書をぱらぱらとめくり、

"こうやれば PC-9801 はビープ音を鳴らすことができるよ"、と。そしてこうすれば、

音は止まるんですよ、と。

ちょっとできそうだと思い、トライしてみました。


先ほどの動かないプログラムを書き換えて、

「ビープ音を鳴らして、キーボードの BS キーが押されたらビープ音を止めて終了するプログラム」としました。

アセンブルして、実行ファイルを作成し実行したところ、ちゃんとビープ音が鳴り、BS キーを押すとビープ音を停止して終了する、という動きが出来ました。

(最後に「DOSへ戻る」という命令を出さないと、プログラムは走ったまま戻ってこない…、というのは知りませんでした)


この真っ黒いコマンド入力の画面が、おれたちには最高に面白いんだ。

――― 今そこにある、PC-9801「本体」という、実感のある世界。

そして、当時、高嶺の花だった「アセンブラ」という新しい世界。

時代遅れの軽トラみたいなパソコンと、

オブジェクト指向や DirectX とは無縁の低級な命令で動く世界が、

こんなにおもしろい! ―――


▼「軽トラみたいなパソコン」とは言いすぎで、「今でも通用する良いデザインのパソコン」です。縦置きにもできますから。


Q: 古いパソコンという閉じた環境でいくら開発しても、新しいことは何もできないのではないか?

A: 古いパソコンの扱いやすい環境でのアセンブラプログラミングに慣れることが出来れば、最近のパソコンでのアセンブラの導入が比較的楽になる、というメリットがあります。


(訪問者のどんなニーズと この記事がつながるか)